When to inject __use__ with functions?

Background

From a previous post How to test a behaviour with function macros? I go the distinct feeling that the community does not see with good eyes the idea of adding several functions to a behaviour’s __use__ macro.

Why we don’t like to put functions into __using__

I get some of the reasons are:

  1. Pollution. By adding functions to the macro, every client that uses your behaviour has them, possibly resulting in a fat API that some clients may not need at all.
  2. It is implicit functions and behaviour VS explicit. The Elixir community prefers explicitness, so having a bunch of functions in a client that no one knows where they come from (because they were injected by use from some behaviour ) is not ideal
  3. Testing such functions can only be done via fake clients, or mocks.

And I think this is it, (feel free to let me know if there are more).

Questions

So, with all this in mind:

  1. Why would anyone inject anything into __using__?
  2. Why do we even need it? Why not just use @behaviour?
  3. What good use cases can it have?

I am looking for answer of any format, tutorials to read, opinions from member, books, anything.
Please do share your mind!

2 Likes

The purpose of __using__ is to inject code. You want to limit doing this (i.e. don’t do it just because you can).

use can be leveraged to inject repetitive code in modules, e.g. in ExUnit the test files have use ExUnit.Case which does a bunch of stuff, among which making it possible to e.g. use test functions in the file.

Imho the only reason to define functions within defmacro __using__, do: … is if that function needs to be compiled using information about the module, which is calling use MyModule or from the options passed to the macro.

If the above does not apply, but you still want to make sure a function is directly callable within MyModule just add an import MyUsedModule with the __using__ macro.

Edit:

To your 2. question: I don’t use __using__ with behaviours unless I have a case where – knowing the module using my behaviour – I can implement functions for that user, which I couldn’t do without knowing that module. Like the child_spec/1 function of GenServer. It needs the module name, but nothing else to be implemented. So it’s much more convenient to have people do use GenServer instead of @behaviour GenServer and they need to copy/paste some functions.

I feel like people coming to elixir often assume behaviours need __using__ because GenServer is using that functionality, which is neither true nor to be recommended as default way for behaviours.

3 Likes

So I understand that __using__ is useful when I want to create a Domain Specific Language, like the one we use to make tests. I understand this argument from Metaprogramming with Elixir but this is such a specific use case that I have never encountered one during my whole career (do note I am starting in Elixir, perhaps here it is more common?).

In this specific case ( of ExUnit ) I wouldn’t mind writting ExUnit.Case.test instead of just test, specially because then I can use alias to abbreviate it to something like T.test or something similar and have the dependency be explicitly visible.

Why not pass that information as parameters to the functions of your behaviour then?

So, you’d do it to provide a default implementation of a function for your clients to (maybe) override?

That doesn’t look that nice and doesn’t feel like a good DSL if you have to always write the module out, or might even stumble over a different alias in about every other testmodule you write.

Because those parameters might be expensive to fetch everytime.

1 Like