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 #1
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

RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
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
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
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
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
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
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
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
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement