From 96fc21dd2ab0e60aeaddb40f7d4c1a02db1789f6 Mon Sep 17 00:00:00 2001 From: Erez Shinan Date: Tue, 13 Aug 2024 15:32:31 +0200 Subject: [PATCH] Dispatch: Improve docs --- docs/dispatch.rst | 39 +++++++++++++++++++++++++-------------- pyproject.toml | 11 ++++++++++- runtype/__init__.py | 31 ++----------------------------- runtype/dispatch.py | 3 ++- 4 files changed, 39 insertions(+), 45 deletions(-) diff --git a/docs/dispatch.rst b/docs/dispatch.rst index 0099c43..36b83d4 100644 --- a/docs/dispatch.rst +++ b/docs/dispatch.rst @@ -1,35 +1,41 @@ 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 `. 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: @@ -37,7 +43,13 @@ Multiple-dispatch allows you to: 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 -------------------- @@ -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' diff --git a/pyproject.toml b/pyproject.toml index 54cd7ad..4bc0020 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" @@ -45,4 +54,4 @@ addopts = "--benchmark-skip --benchmark-warmup=on --benchmark-warmup-iterations= [tool.ruff] line-length = 120 -target-version = "py311" \ No newline at end of file +target-version = "py311" diff --git a/runtype/__init__.py b/runtype/__init__.py index d5c4fa6..ad4719a 100644 --- a/runtype/__init__.py +++ b/runtype/__init__.py @@ -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) diff --git a/runtype/dispatch.py b/runtype/dispatch.py index d6937e9..2472ee1 100644 --- a/runtype/dispatch.py +++ b/runtype/dispatch.py @@ -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]) @@ -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] = ()):