@@ -5611,11 +5611,10 @@ defmodule Kernel do
56115611 and arity, then the overridable ones are discarded. Otherwise, the
56125612 original definitions are used.
56135613
5614- It is possible for the overridden definition to have a different visibility
5615- than the original: a public function can be overridden by a private
5616- function and vice-versa.
5617-
5618- Macros cannot be overridden as functions and vice-versa.
5614+ It is possible for the overridden definition to have a different
5615+ visibility than the original: a public function can be overridden
5616+ by a private function and vice-versa. Macros cannot be overridden
5617+ as functions and vice-versa.
56195618
56205619 ## Example
56215620
@@ -5642,31 +5641,15 @@ defmodule Kernel do
56425641 As seen as in the example above, `super` can be used to call the default
56435642 implementation.
56445643
5645- > #### Disclaimer {: .tip}
5646- >
5647- > Use `defoverridable` with care. If you need to define multiple modules
5648- > with the same behaviour, it may be best to move the default implementation
5649- > to the caller, and check if a callback exists via `Code.ensure_loaded?/1` and
5650- > `function_exported?/3`.
5651- >
5652- > For example, in the example above, imagine there is a module that calls the
5653- > `test/2` function. This module could be defined as such:
5654- >
5655- > defmodule CallsTest do
5656- > def receives_module_and_calls_test(module, x, y) do
5657- > if Code.ensure_loaded?(module) and function_exported?(module, :test, 2) do
5658- > module.test(x, y)
5659- > else
5660- > x + y
5661- > end
5662- > end
5663- > end
5664-
56655644 ## Example with behaviour
56665645
5667- You can also pass a behaviour to `defoverridable` and it will mark all of the
5668- callbacks in the behaviour as overridable:
5646+ `defoverridable` is commonly used with behaviours. The behaviours use
5647+ `@callback` definitions to define the general module API and the
5648+ `__using__` callback is used to define default implementations of
5649+ functions, which can then be overridable.
56695650
5651+ For convenience, you can pass a behaviour to `defoverridable` and it
5652+ will mark all of the callbacks in the behaviour as overridable:
56705653
56715654 defmodule Behaviour do
56725655 @callback test(number(), number()) :: number()
@@ -5694,6 +5677,52 @@ defmodule Kernel do
56945677 end
56955678 end
56965679
5680+ > #### Narrow behaviours and entry points {: .tip}
5681+ >
5682+ > When defining behaviours, a general rule of thumb is to define narrow
5683+ > behaviours, with the minumum amount of callbacks, to facilitate maintenance
5684+ > over time. Fewer callbacks minimize the points of contact between different
5685+ > parts of the system and reduces the risk of breaking changes and of different
5686+ > implementations having inconsistent behaviour. However, when using `defoverridable`
5687+ > with behaviours, you may accidentally define broad interfaces as all default
5688+ > behaviour is provided via `defoverridable`. Furthermore, `defoverridable`
5689+ > necessarily relies on meta-programming, which complicates debugging. `super` is
5690+ > also hard to troubleshoot, as it by definition relies on calling an implicitly
5691+ > defined function.
5692+ >
5693+ > A possible alternative to `defoverridable` is to use optional callbacks and
5694+ > move the default implementation to the caller. Then you can check if a callback
5695+ > exists via `Code.ensure_loaded?/1` and `function_exported?/3`. For instance,
5696+ > in the example above, imagine there is a module that calls the `test/2` function.
5697+ > This module could be defined as such:
5698+ >
5699+ > defmodule CallsTest do
5700+ > def receives_module_and_calls_test(module, x, y) do
5701+ > if Code.ensure_loaded?(module) and function_exported?(module, :test, 2) do
5702+ > module.test(x, y)
5703+ > else
5704+ > x + y
5705+ > end
5706+ > end
5707+ > end
5708+ >
5709+ > The downside of the above code is that it must call `Code.ensure_loaded?/1` and
5710+ > `function_exported?/3` on every invocation of the behaviour, which may impact
5711+ > runtime performance. For this reason, this approach works best when the behaviour
5712+ > has an entry point, such as a `init` callback (as seen in `GenServer`), which you
5713+ > invoke once to guarantee the module is loaded, and from that moment, you only need
5714+ > to perform `function_exported?/3` checks.
5715+ >
5716+ > To recap:
5717+ >
5718+ > * Prefer narrow behaviours
5719+ >
5720+ > * If your behaviour has an entry point, consider using optional callbacks
5721+ > followed by `Code.ensure_loaded?/1` and `function_exported?/3` checks
5722+ >
5723+ > * If using `defoverridable`, avoid relying on `super` to trigger the default
5724+ > behaviour, suggesting users to invoke well-defined APIs instead.
5725+ >
56975726 """
56985727 defmacro defoverridable ( keywords_or_behaviour ) do
56995728 quote do
0 commit comments