You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Multimethods are a form of runtime polymorphism that may feel familiar to users of type-based multiple dispatch.
736
+
Multimethods are strictly more powerful than strictly type-based dispatch systems, however.
737
+
Multimethods dispatch to methods via a user-defined dispatch function which has access to the full runtime value of every argument passed to the final function.
738
+
The value returned from a dispatch function can be any hashable value.
739
+
740
+
Methods are selected by looking up the returned dispatch value in a mapping of dispatch values to methods.
741
+
Dispatch values are compared to the stored method mappings using :lpy:fn:`isa?` which naturally supports both the usage of the :ref:`hierarchy <hierarchies>` system for sophisticated hierarchical data relationships and the Python type system.
742
+
If no method is found for the dispatch value, the default dispatch value (which defaults to ``:default`` but may be selected when the multimethod is defined) will be used to look up a method.
743
+
If no method is found after consulting the default value, a :external:py:exc:`NotImplementedError` exception will be thrown.
744
+
745
+
Users can create new multimethods using the :lpy:fn:`defmulti` macro, specifying a dispatch function, an optional default dispatch value, and a hierarchy to use for :lpy:fn:`isa?` calls.
746
+
Methods can be added with the :lpy:fn:`defmethod` macro.
747
+
Methods can be introspected using :lpy:fn:`methods` and :lpy:fn:`get-method`.
748
+
Methods can be individually removed using :lpy:fn:`remove-method` or completely removed using :lpy:fn:`remove-all-methods`.
749
+
750
+
It is possible using both hierarchies and Python's type system that there might be multiple methods corresponding to a single dispatch value.
751
+
Where such an ambiguity exists, Basilisp allows users to disambiguate which method should be selected when a conflict arises between 2 method dispatch keys using :lpy:fn:`prefer-method`.
752
+
Users can get the mapping of method preferences by calling :lpy:fn:`prefers` on the multimethod.
753
+
754
+
The following example shows a basic multimethod using a keyword to dispatch methods based on a single key in a map like a discriminated union.
755
+
The :ref:`hierarchies` section shows a more advanced example using hierarchies for method dispatch.
If your primary use case for a multimethod is dispatching on the input type of the first argument of a multimethod, consider using a :ref:`protocol <protocols>` instead.
781
+
Protocols are almost always faster for single-argument type based dispatch and require no manual specification of the dispatch function.
Basilisp supports creating ad-hoc hierarchies which define relationships as data.
793
+
Hierarchies are particularly useful for :ref:`multimethods`, but may also be used in other contexts.
736
794
737
-
.. seealso::
795
+
Create a new hierarchy with :lpy:fn:`make-hierarchy`.
796
+
Define relationships within that hierarchy using :lpy:fn:`derive`.
797
+
Relationships are between tags and their parent where tags are valid Python types or a namespace qualified-keyword and parents are namespace-qualified keywords.
798
+
This allows users to slot concrete host types into hierarchies, which is particularly useful in the context of :ref:`multimethods`.
799
+
Note however that hierarchies do not allow Python types to be defined as parents, because that would ultimately cause the hierarchy to diverge from the true class hierarchy on the host.
If no hierarchy argument is provided to hierarchy functions, a default global hierarchy is used.
848
+
To avoid conflating hierarchies, you should create your own hierarchy which you pass to the various hierarchy library functions.
849
+
850
+
.. warning::
851
+
852
+
Hierarchies returned by :lpy:fn:`make-hierarchy` are immutable.
853
+
To modify a hierarchy as by :lpy:fn:`derive` or :lpy:fn:`underive`, treat it like Basilisp's other immutable data structures:
854
+
855
+
.. code-block::
856
+
857
+
(let [h (-> (make-hierarchy)
858
+
(derive ::banana ::fruit)
859
+
(derive ::apple ::fruit))]
860
+
;; ...
861
+
)
862
+
863
+
For hierarchies that need to be modified at runtime, consider storing the hierarchy in a Ref such as an :ref:`atom <atoms>` and using ``(swap! a derive ...)`` to update the hierarchy.
864
+
865
+
.. warning::
866
+
867
+
:lpy:fn:`isa?` is not the same as :lpy:fn:`instance?`.
868
+
The former operates on both hierarchy members and valid Python types, but cannot check if an object is an instance of a certain type.
869
+
In this way it is much more like the Python :external:py:func:`issubclass`.
Most of Basilisp's core functionality is written in terms of interfaces and abstractions, rather than concrete types.
881
+
The base interface types are (necessarily) all written in Python, however.
882
+
Basilisp cannot generate such interface types however, which limits its ability to create similar abstractions.
883
+
884
+
Protocols are the Basilisp-native solution to defining interfaces.
885
+
Protocols are defined as a set of functions and their associated signatures without any defined implementation (and optional docstrings).
886
+
Once created a protocol defines both an interface (a :external:py:class:`abc.ABC`) and a series of stub functions that dispatch to actual implementations based on the type of the first argument.
887
+
888
+
Users can define implementations protocol methods for any type using :lpy:fn:`extend` or the convenience macros :lpy:fn:`extend-protocol` and :lpy:fn:`extend-type`.
889
+
Type dispatch respects the Python type hierarchy, so implementations may be defined against other interface types or parent types and the most specific implementation will always be selected for the provided object.
890
+
You can fetch the collection of types which explicitly implement a Protocol using :lpy:fn:`extenders` (this will not include types which inherit from the Protocol interface, however).
891
+
However, it is possible to check if a type extends a protocol (including those types which inherit from the interface) using :lpy:fn:`extends?`.
892
+
It is possible to check if a type satisfies (e.g. implements) a Protocol using :lpy:fn:`satisfies?`.
893
+
894
+
Because Protocols ultimately generate an interface type, they may be used as an interface type of :ref:`data_types_and_records`.
895
+
Likewise, this enables Python code to participate in Protocols by referencing the generated interface.
896
+
897
+
Protocols provide a natural solution to many different problems.
898
+
As an example, :lpy:ns:`basilisp.json` uses Protocol-based dispatch for converting values into their final JSON representation.
899
+
Protocols allow other code to participate in that serialization without needing to modify the source.
900
+
Suppose you wanted to serialize :external:py:class:`datetime.datetime` instances out as Unix Epochs rather than as ISO-8601 formatted strings, you could provide a custom protocol implementation to do just that.
901
+
902
+
.. code-block::
903
+
904
+
;; Abbreviated protocol definition copied from basilisp.json
0 commit comments