6

I am just working through Dave Thomas' book and am currently exploring Protocols in Elixir. I know I can fallback to a generic protocol implementation with @fallback_to_any true. For example:

defprotocol Caesar do
  @fallback_to_any true
  def encrypt(string, shift)
  def rot13(string)
end

defimpl Caesar, for: BitString do
  def encrypt(string, shift) do
    # implementation for binary
  end

  def rot13(string) do
    # implementation for binary
  end
end

defimpl Caesar, for: List do
  def encrypt(string, shift) do
    # implementation for character list
  end

  def rot13(string) do
    # implementation for character list
  end
end

defimpl Caesar, for: Any do
  def encrypt(string, shift) do
    # generic implementation
  end

  def rot13(string) do
    # generic implementation
  end
end

However in the above case, basically every implementation of rot13/1 would look like this:

def rot13(string) do
  Caesar.encrypt(string, 13)
end

Still, I currently have to repeat this in every implementation, which seems utterly wrong. Is there a way to share rot13/1 across all implementations of Caesar?

Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168

1 Answers1

14

So I found this question about delegation in implementations. It made me wonder whether defdelegate works inside protocols and indeed it does! So a possible solution to this is as follows:

defmodule Caesar.Shared do
  def rot13(string) do
    Caesar.encrypt(string, 13)
  end
end

defprotocol Caesar do
  def encrypt(string, shift)
  defdelegate rot13(string), to: Caesar.Shared
end

defimpl Caesar, for: BitString do
  def encrypt(string, shift) do
    # implementation for binary
  end
end

defimpl Caesar, for: List do
  def encrypt(string, shift) do
    # implementation for character list
  end
end

I'm still interested in other answers. Maybe there is still a better, more idiomatic way to do this directly inside the protocol definition.

Community
  • 1
  • 1
Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
  • 5
    Your solution is definitely the way to go! – José Valim Sep 25 '14 at 07:27
  • @JoséValim thanks! I'm not sure about the module name `Caesar.Shared` though. Is there a naming convention for this, or is it yet to be established? – Patrick Oscity Sep 25 '14 at 07:52
  • In a lot of places the named convention is Utils but I wouldn't worry about it. As the project grows, you will likely find a good place/name to host such related functions together. – José Valim Sep 25 '14 at 09:40
  • As of now (master branch for elixir 1.13), this solution does not work anymore. As stated in https://github.com/elixir-lang/elixir/issues/11347#issuecomment-951359916, support for defdelegate inside a protocol was only incidental, and it was removed – epilgrim Oct 26 '21 at 08:35
  • @epilgrim thanks for taking the time to comment here! Do you happen to know a better way of doing this? – Patrick Oscity Oct 26 '21 at 12:36