How to make a module depend on a behaviour?

There’s a few steps:

x = Car.WingsWithNoBreaks
c = Car.new("honda", x)

Here there’s nothing to be done at compile time / Car.new. From the AST level all it gets is a variable x and that’s it. What x will be at runtime is unknown to the AST.

The typesystem upcoming might have more information (as in that x will always resolve to Car.WingsWithNoBreaks in this specific case), but that’s the next step:

c = Car.new("honda", Car.WingsWithNoBreaks)

Here the AST is at least aware of a specific value – an atom. That atom might or might not reference a module. It might not reference a module when this code is compiled, but might successfully do so once this code is run at runtime.

Again go the optimistic route: The atom references an existing module and it doesn’t change up until the code is actually run at runtime.

You can check of the module has functions as defined on the behaviour using function_exported?. That does check if functions of the correct name and arity are exported by the module.

It still doesn’t check that those functions can handle all the inputs they will eventually get provided, nor will it check that those functions return values expected to be returned based on the interface of the behaviour. That’s again information that cannot be gleamed from looking at the elixir AST (basically the source code in different form).

This is again a place where the new upcoming typesystem may eventually be able to check that stuff, but by now it doesn’t do so.

And all of that is assuming the callback implementations are actually correct and don’t have bugs, which happen to not violate the interface, but expectations not encodable in typespecs.

So all in all:

  • No this is not “but surely this can be done”
  • If attempted it would be full of footguns and false positives.
  • Even if you do what can feasably be done today you’re still checking just a subset of error cases, so you cannot get rid of handling error cases at runtime anyways
  • Some refactoring like extracting the module name to a variable or even another function makes this problem vastly more difficult outside of a typesystem, given how common that is it’s questionable if the effort to implement the above is worthwhile at all
3 Likes