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).
fireproofsocks

fireproofsocks

Thank you for so many educational responses! So the name “callbacks” DOES make sense, but ONLY for the parse! function where we are passing in a module and then calling a method on it. I still don’t see any callbacks happening for our regular behaviour implementations. How about the following?:

Parser.parse(AnotherParser, contents)

That would make sense: at least that looks like it is performing a dispatching/callback option, but of course that does not work because function Parser.parse/2 is undefined or private.

So when we actually define a @callback and an @impl of it, then as far as I can see, we aren’t actually DOING a callback. We are just enforcing that implementations follow our behaviour blueprint in the same way that OO languages implement an interface and must implement the required methods.

When we define the parse!/2 function and provide a module and actually DO module dispatch via a callback, then we aren’t actually directly using anything to do with the @callback or @impl functions. In principle, we could do that entire module-dispatch and callback stuff entirely without any of the @behaviour, @callback, or @impl stuff.

Forgive me for being dense, but the naming convention here still does not make sense to me. I would not call those @callbacks, I would call them “signatures”, “members”, “actions”, or almost anything other than a loaded term like “callbacks” because nothing about them requires a callback: you can write them without delving into behaviours at all! “Callbacks” really seems like the wrong word for what is going on here and it has sent my brain on a wild goose chase. It seems that they were named after HOW they get typically used and NOT after what they actually ARE. For me at least, this has caused a lot of mental friction.

Summary

The best I can sum up for myself after the patient guidance of the contributors here is this:

  1. The name @callbacks can be a confusing misnomer! A @callback simply defines a function signature that every implementation of the behaviour must define. This is conceptually the same as an Object Oriented class implementing all of the functions defined by an interface.
  2. The practice of using “dynamic dispatch” works well with behaviours: your “dispatching” function can accept a module as input, then call a method on that module. Behaviours offer a way to guarantee that the module provided as input has implemented the function that you wish to call on it. E.g. in the following example, you had better be 100% sure that your provided target module has implemented the do_something/1 function:
    def dispatch_to_module(target_module, args) do
        target_module.do_something(args)
    end
asummers

asummers

Not to detract from your point, but the name callback in the Erlang case is 30+ years old at this point. Some of the original authors that pop around here every now and then (rvirding from above, e.g.) may be able to comment on the etymology of the name but due to the telephony nature of Erlang, I think it may actually be referencing a literal (phone) call back. GenServer has a concept called call so I would imagine it stemmed from the generic behavior around GenServers. That’s pure speculation, however.

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
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
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
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

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
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
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
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
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
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
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New

We're in Beta

About us Mission Statement