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
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
The major benefit is compile-time checking:
- if
CustomerAsays@behaviour Handlerbut 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
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
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
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.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









