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










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
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.
I swapped
implementationwithcallback_module- so it makes sense thatcallback_module.parse/1is a callback function.Another example from the Access behaviour
The
fetch/2function implemented byAccessgrabs the callback module from thestructso it can turn around and call themodule.fetch/2callback function.With behaviours you are composing the callback module with the behaviour module - there is no inheritance.
fireproofsocks
Thank you for the explanation, but I still don’t follow the execution flow here. When you call a function on an implementation, I don’t see how that has anything to do with the behaviour module other than to reference the function signature. I guess I don’t follow what happens when you use
@implabove the function…Could you field an example of how to actually call the
parse!()function? I assume you would do something likeParser.parse!(MyParser, contents), but that syntax just seems really backwards.asummers
@impl MyBehaviourwould give a compiler error if you missed a callback that you were required to implement per the behaviour or if the function that has the@implis not actually part of the behaviour.E.g. if I have
then you would get a compiler error. Similarly,
would also give a compiler error. I actually don’t remember if it’s a warning or an error, but we run
mix compile --warnings-as-errorsso they’re the same to us.@implis purely a safeguard to help against changing behaviours and during refactoring, but it’s optional.NobbZ
You have not yet used GenServers? They do it very similar to this…
peerreynders
Just to underline this point: example
The
Demomodule is the callback module. None of the usualbehaviourceremony is actually necessary - other than implementing the mandatory callback functions.The
Democallback module is composed with theGenServerbehaviour module leading to aGenServerbased process with the specializedDemofunctionality.In OO:
parse!/1would 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 methoddo_parse/1that is left for the subclass to implement the content specific parsing.do_parse/1implementing the content specific implementation. You use the subclass to parse the content it is specialized for.So:
Parser.parse!/2is the generic function in the behaviour module that will delegate the details of parsing the specific content toMyParser.MyParseris a collection of hook function implementations needed to parse the specific content (organized as a callback module).LostKobrakai
It’s backwards because MyParser is known and you could just call
MyParser.parse/1. But the code using a callback module most often don’t know said callback module (MyParser) in advance, but rather it’s configured or passed in at runtime / startup. This is where behaviours shine. Like OTP has the :gen_server behaviour shipped with erlang, but you are implementing the callback module to make it actually do something in your project. Another example is swoosh/bamboo. They just say an adapter needs to implement a set of function to be usable by them and anybody can implement is using their favorite email service.This also makes those modules be comparable to callback functions. You pass your own implementation of something into pre-existing code, which can then call into your supplied implementation whenever needed. It’s just that a callback function is just a single function, but a callback module for a behaviour can bundle multiple functions.
peerreynders
The free sample of Designing for Scalability with Erlang/OTP: Implement Robust, Fault-Tolerant Systems on Google play has the complete Chapter 3. Behaviors. The code is in Erlang - for the equivalent Elixir code see below. Points of note:
Frequencycallback module injects itself into theServerbehaviour module through itsstart/0function.Serverbehaviour moduleloop/2function keeps the callback module that the process was initialized with as part of the loop state. The arguments to the loop are 1.) the callbackmodule, 2.) thestatewhich is unique to this particular process and transformed by the callback module.The callbacks implemented by the
Frequencycallback module are:init/1(used inServer.init/2)handle/2(used inServer.loop/2)terminate/1(used inServer.loop/2)Frequencybefore behaviour separationAfter split into the generic
Serverbehaviour module and the specificFrequencycallback modulefireproofsocks
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?: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
@callbackand an@implof 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!/2function and provide a module and actually DO module dispatch via a callback, then we aren’t actually directly using anything to do with the@callbackor@implfunctions. In principle, we could do that entire module-dispatch and callback stuff entirely without any of the@behaviour,@callback, or@implstuff.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:
@callbackscan be a confusing misnomer! A@callbacksimply 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.do_something/1function: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.
GenServerhas a concept calledcallso I would imagine it stemmed from the generic behavior around GenServers. That’s pure speculation, however.NobbZ
In erlang many terms are meant a bit different than what I’ve learnt those terms mean from CS courses. I learnt to deal with it. You should do so as well. Many “modern” terms only got their meaning a couple of years ago, while they are used in erlang a lot longer…
Callback might be one of these, which got its negative conotation only recently through JavaScript…