CharlesO

CharlesO

A better Module interaction pattern?

I’m looking to improve this pattern.
It is a pattern I use in a lot of my projects.

defmodule Handler do
    def handle_request(data) do
        response = apply(@some_module_from_config, :parse, [data])

        # do_something_with_response(response)
        ...
    end
end

I then have customer specific implementations of :parse in separate modules, for example

defmodule CustomerA do
    def parse(data) do
        # specific parsing of this data for Customer-A
    end
end

defmodule CustomerB do
    def parse(data) do
         # specific parsing of this data for Customer-B
    end
end

This works, but can it be improved?

Is there a benefit for example to have a @behaviour in Handler which defines :parse and other functions

Then each Customer module no implements use Handler

Please are their better patterns for doing this?

Thanks.

Marked As Solved

D4no0

D4no0

Nope, all you need is something like this:

defmodule ParseBehavior do
  # Obviously replace any with your data types
  @callback parse(data :: any) :: any
end

defmodule CustomerA do
  @behaviour ParseBehavior 
end

If you don’t implement the specified callbacks you will get a compilation warning.

You can also have the variant with the default implementation:

defmodule ParseBehavior do
  @callback parse(data :: any) :: any

  defmacro __using__(_opts) do
    quote do
      @behaviour ParseBehavior
      
      def parse(data) do
        # This will be the default implementation if not overriden
      end

     defoverridable parse: 1
    end
  end
end

defmodule CustomerA do
   use ParseBehavior

  # This definition will override the default definition
  def parse(data) do
   
  end
end

I wouldn’t recommend using the variant with default implementation unless that is strictly required, as it makes the code harder to read.

Also Liked

al2o3cr

al2o3cr

The major benefit is compile-time checking:

  • if CustomerA says @behaviour Handler but doesn’t supply callbacks with the correct arities, the compiler will complain
  • (sometimes) Dialyzer will be able to prove that a function’s implementation fails to match the behaviour’s spec

As a side bonus, libraries like mox can use that same behaviour for their definitions.

Eiji

Eiji

No need to use apply as it’s much more useful if you have function name stored in variable.

`        response = @some_module_from_config.parse(data)`

Not only it would not give you any benefit, but it would also be wrongly used.

In Handler module you have to define callbacks and / or macrocallbacks. You use @behaviour attribute in CustomerA and CustomerB modules. In this tiny example it’s not important at all, but in general you use this to make sure that all callbacks are implemented as Elixir gives compilation warning otherwise.

egze

egze

I also recommend watching the keynote from José https://www.youtube.com/watch?v=agkXUp0hCW8
He goes into the topic what to use when.

Last Post!

D4no0

D4no0

You can’t, the module that defines behavior whole point is to make sure that you are implementing the required functions, it’s up to you how you call them afterwards.

You can either use the pattern you are already using with apply/3 or you can call directly the function, as module names are just plain atoms:

my_modules = [CustomerA, CustomerB]
for module <- my_modules do
  module.parse(data)
end

You could add some compile-time checks to ensure that the module you are calling has the behavior declared, but I don’t see a lot of value from that.

Where Next?

Popular in Questions Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement