@@ -5611,11 +5611,10 @@ defmodule Kernel do
5611
5611
and arity, then the overridable ones are discarded. Otherwise, the
5612
5612
original definitions are used.
5613
5613
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.
5619
5618
5620
5619
## Example
5621
5620
@@ -5642,31 +5641,15 @@ defmodule Kernel do
5642
5641
As seen as in the example above, `super` can be used to call the default
5643
5642
implementation.
5644
5643
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
-
5665
5644
## Example with behaviour
5666
5645
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.
5669
5650
5651
+ For convenience, you can pass a behaviour to `defoverridable` and it
5652
+ will mark all of the callbacks in the behaviour as overridable:
5670
5653
5671
5654
defmodule Behaviour do
5672
5655
@callback test(number(), number()) :: number()
@@ -5694,6 +5677,52 @@ defmodule Kernel do
5694
5677
end
5695
5678
end
5696
5679
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
+ >
5697
5726
"""
5698
5727
defmacro defoverridable ( keywords_or_behaviour ) do
5699
5728
quote do
0 commit comments