Skip to content

Commit

Permalink
Dispatch: Improve docs
Browse files Browse the repository at this point in the history
  • Loading branch information
erezsh committed Aug 13, 2024
1 parent 373aef0 commit 96fc21d
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 45 deletions.
39 changes: 25 additions & 14 deletions docs/dispatch.rst
Original file line number Diff line number Diff line change
@@ -1,43 +1,55 @@
Dispatch
========

Provides a decorator that enables multiple-dispatch for functions.
Provides a decorator that enables multiple-dispatch for functions. Inspired by Julia.

Features:

- Full specificity resolution

- Mypy support
- Partial mypy support

(Inspired by Julia)
- Fast

See :ref:`benchmarks <benchmarks-dispatch>`.

Decorator
---------

.. autofunction:: runtype.multidispatch

.. autofunction:: runtype.Dispatch

.. autoclass:: runtype.dispatch.MultiDispatch
:members: choices, feed_token, copy, pretty, resume_parse, exhaust_lexer, accepts, as_immutable

.. autoclass:: runtype.dispatch.DispatchError



What is multiple-dispatch?
--------------------------

Multiple-dispatch is an advanced technique for structuring code, that complements object-oriented programming.

Unlike in OOP, where the type of the "object" (or: first argument) is always what determines the dispatch, in multiple-dispatch all the arguments decide together, according the idea of specificity: The more specific classes (i.e. subclasses) get picked before the more abstract ones (i.e. superclasses).
You can think of multiple-dispatch as function overloading on steroids.

In OOP, the type of the object (aka the first argument) always determines the dispatch. Methods of subclasses override the methods of their superclasses. In other words, the more specific type is chosen over the less specific type.

That means that when you need to define a logical operation that applies to several types, you can first solve the most abstract case, and then slowly add special handling for more specific types as required. If you ever found yourself writing several "isinstance" in a row, you could probably use multiple-dispatch to write better code!
In multiple-dispatch, all the arguments decide together, according the same idea of specificity: The more specific classes (i.e. subclasses) get picked over the less specific types (i.e. superclasses). In cases when the dispatch is ambiguous, which will happen if different parameters can't agree on the correct dispatch, an error will be thrown.

Multiple-dispatch allows you to:

1. Write type-specific functions using a dispatch model that is much more flexible than object-oriented.

2. Group your functions based on "action" instead of based on type.

You can think of multiple-dispatch as function overloading on steroids.
3. Replace long sequences of "if isinstance" statements


A common way to use multiple-dispatch, is to first implement the most abstract case, and then slowly add special handling for more specific types as required.

It is particularly useful for visiting ASTs.


Runtype's dispatcher
--------------------
Expand Down Expand Up @@ -166,14 +178,13 @@ Dispatch is designed to always throw an error when the right choice isn't obviou
Another example:
::

@md
def join(seq, sep: str = ''):
return sep.join(str(s) for s in seq)
>>> @md
... def join(seq, sep: str = ''):
... return sep.join(str(s) for s in seq)

@md
def join(seq, sep: list):
return join(join(sep, str(s)) for s in seq)
...
>>> @md
... def join(seq, sep: list):
... return join(join(sep, str(s)) for s in seq)

>>> join([0, 0, 7]) # -> 1st definition
'007'
Expand Down
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ pytest-benchmark = "*"
# plum-dispatch = "*"
# multipledispatch = "*"

[tool.poetry.group.docs]
optional = true

[tool.poetry.group.docs.dependencies]
recommonmark = "*"
sphinx-gallery = "*"
sphinx_markdown_tables = "*"
sphinx_rtd_theme = ">=1.2"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Expand All @@ -45,4 +54,4 @@ addopts = "--benchmark-skip --benchmark-warmup=on --benchmark-warmup-iterations=

[tool.ruff]
line-length = 120
target-version = "py311"
target-version = "py311"
31 changes: 2 additions & 29 deletions runtype/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,10 @@ def Dispatch(typesystem: TypeSystem = PythonTyping()):
"""Creates a decorator attached to a dispatch group,
that when applied to a function, enables multiple-dispatch for it.
Will be deprecated in future versions.
Parameters:
typesystem (Typesystem): Which type-system to use for dispatch. Default is Python's.
Example:
::
>>> from runtype import Dispatch
>>> dp = Dispatch()
>>> @dp
... def add1(i: Optional[int]):
... return i + 1
>>> @dp
... def add1(s: Optional[str]):
... return s + "1"
>>> @dp
... def add1(a): # Any, which is the least-specific
... return (a, 1)
>>> add1(1)
2
>>> add1("1")
11
>>> add1(1.0)
(1.0, 1)
"""

return MultiDispatch(typesystem)
Expand Down
3 changes: 2 additions & 1 deletion runtype/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


class DispatchError(Exception):
pass
"Thrown whenever a dispatch fails. Contains text describing the conflict."


# TODO: Remove test_subtypes, replace with support for Type[], like isa(t, Type[t])
Expand All @@ -21,6 +21,7 @@ class MultiDispatch:
Parameters:
typesystem - instance for interfacing with the typesystem
test_subtypes: indices of params that should be matched by subclass instead of isinstance.
(will be soon deprecated, and replaced by using Type[..] annotations)
"""

def __init__(self, typesystem: TypeSystem, test_subtypes: Sequence[int] = ()):
Expand Down

0 comments on commit 96fc21d

Please sign in to comment.