Protocols vs interfaces

Not sure what you mean by that! Generally we want an interface because what we actually need is a behaviour but each implementation has state. I am currently working on a personal project where I have a credentials store. In tests, the state is a map of strings but in prod it will be either be a database or a file. So your behaviour needs to keep that map, or the table details, and it is abstract from the outside. But a behaviour would not cut it because I do not want a process (though some implementations states could be a pid).

Now the credentials store could be a simple fun ; you pass the credentials name you want and it returns the token. In that way, using lambda functions provides actually better polymorphism than objects. Alas, you not only want get_gredentials but also put_credentials, list_credentials, has_credentials?, etc…

You can always pass funs like this:

  defp wrap_creds(creds) do
    fn
      :get, name ->
        {creds[name], creds}

      :put, {name, token} ->
        creds = Map.put(creds, name, token)
        {creds, wrap_creds(creds)}

      :has?, name ->
        {Map.has_key?(creds, name), creds}
    end

But those are poor man’s objects.

Another example I had with my team was to extract environment variables from .env files, gitlab CI yaml files, docker compose files, env documentation toml files, application.properties java files. Here the different data structures are only created to support the protocol, they don’t do anything else besides listing, getting and putting the keys.

For those cases I do just what Ipil does but now with a shortcut macro.