fireproofsocks

fireproofsocks

Why are behaviour methods referred to as "callbacks"?

Coming from an OO background, my first reaction is to think of behaviours as interfaces. So it makes sense that a module must define one or more functions outlined in a behaviour if it wishes to implement that behaviour.

So why in Elixir are the behaviour’s functions referred to as “callbacks”? In other languages, a “callback” has quite different connotations which have little to do with inheritance.

In fact, the page at https://elixir-lang.org/getting-started/typespecs-and-behaviours.html has left me scratching my head, especially the section on “Dynamic dispatch”. Since “callbacks” are apparently not at all what you might think, it makes me really wonder what exactly is meant by “dynamic dispatch”. There are no clarification or examples in that section (!!!), only a reassurance that “you don’t need to define a behaviour in order to dynamically dispatch on a module, but those features often go hand in hand.”

Can someone help shed light on this? Thank you!

Most Liked

peerreynders

peerreynders

In simple terms the “behaviour” already exists as the behaviour module capturing the generic parts. You are implementing the callback module which contains the specific parts. To execute those specific parts the behaviour module calls the “callback” functions on the callback module.

The behaviour module is passed a callback module much in the same way a function is passed a callback function - the difference is that the behaviour module expects certain functions to be implemented by the callback module rather than there just being that one function.

defmodule Parser do
  @callback parse(String.t) :: {:ok, term} | {:error, String.t}
  @callback extensions() :: [String.t]

  def parse!(callback_module, contents) do
    case callback_module.parse(contents) do
      {:ok, data} -> data
      {:error, error} -> raise ArgumentError, "parsing error: #{error}"
    end
  end
end

I swapped implementation with callback_module - so it makes sense that callback_module.parse/1 is a callback function.

Another example from the Access behaviour

 def fetch(%module{} = container, key) do
    module.fetch(container, key)
  rescue
    exception in UndefinedFunctionError ->
      raise_undefined_behaviour(exception, module, {^module, :fetch, [^container, ^key], _})
  end

The fetch/2 function implemented by Access grabs the callback module from the struct so it can turn around and call the module.fetch/2 callback function.

With behaviours you are composing the callback module with the behaviour module - there is no inheritance.

11
Post #2
peerreynders

peerreynders

Thought experiment - referring to the Frequency callback module from my last post

def start,
    do: Server.start(__MODULE__, [])

That sample code (server.exs) can be easily rewritten to work in the following fashion:

def start,
    do: Server.start(
          &__MODULE__.init/1,
          &__MODULE__.handle/2,
          &__MODULE__.terminate/1,
          []
        )

Are init/1, handle/2, terminate/1 callbacks now?

Wouldn’t developers say: Why can’t I just pass the module that implements those functions?

peerreynders

peerreynders

You have not yet used GenServers? They do it very similar to this…

Just to underline this point: example

The Demo module is the callback module. None of the usual behaviour ceremony is actually necessary - other than implementing the mandatory callback functions.

{:ok, pid} = GenServer.start_link(Demo,[])

The Demo callback module is composed with the GenServer behaviour module leading to a GenServer based process with the specialized Demo functionality.


In OO:

  • parse!/1 would be a method on the abstract class. It may have some generic logic but doesn’t do the actual work. It would call a hook method do_parse/1 that is left for the subclass to implement the content specific parsing.
  • The subclass overides do_parse/1 implementing the content specific implementation. You use the subclass to parse the content it is specialized for.

So:

  • Parser.parse!/2 is the generic function in the behaviour module that will delegate the details of parsing the specific content to MyParser.
  • MyParser is a collection of hook function implementations needed to parse the specific content (organized as a callback module).

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
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
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
nobody
Hi! In PHP: $SERVER['SERVERADDR'] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New

Other popular topics Top

mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I fore...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
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
hariharasudhan94
Lets say i have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => "XX...
New

We're in Beta

About us Mission Statement