# [Plum: Multiple Dispatch in Python](https://github.com/wesselb/plum)
[![CI](https://github.com/wesselb/plum/workflows/CI/badge.svg?branch=master)](https://github.com/wesselb/plum/actions?query=workflow%3ACI)
[![Coverage Status](https://coveralls.io/repos/github/wesselb/plum/badge.svg?branch=master&service=github)](https://coveralls.io/github/wesselb/plum?branch=master)
[![Latest Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://wesselb.github.io/plum)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
Everybody likes multiple dispatch, just like everybody likes plums.
* [Installation](#installation)
* [Basic Usage](#basic-usage)
* [Scope of Functions](#scope-of-functions)
* [Classes](#classes)
- [Forward References](#forward-references)
* [Keyword Arguments and Default Values](#keyword-arguments-and-default-values)
* [Comparison with `multipledispatch`](#comparison-with-multipledispatch)
* [Type System](#type-system)
- [Union Types](#union-types)
- [Parametric Types](#parametric-types)
- [Variable Arguments](#variable-arguments)
- [Return Types](#return-types)
* [Conversion and Promotion](#conversion-and-promotion)
- [Conversion](#conversion)
- [Promotion](#promotion)
* [Advanced Features](#advanced-features)
- [Abstract Function Definitions](#abstract-function-definitions)
- [Method Precedence](#method-precedence)
- [Parametric Classes](#parametric-classes)
- [Hooking Into Type Inference](#hooking-into-type-inference)
- [Add Multiple Methods](#add-multiple-methods)
- [Extend a Function From Another Package](#extend-a-function-from-another-package)
- [Directly Invoke a Method](#directly-invoke-a-method)
## Installation
Plum requires Python 3.6 or higher.
```bash
pip install plum-dispatch
```
## Basic Usage
Multiple dispatch allows you to implement multiple *methods* for the same
*function*, where the methods specify the types of their arguments:
```python
from plum import dispatch
@dispatch
def f(x: str):
return "This is a string!"
@dispatch
def f(x: int):
return "This is an integer!"
```
```python
>>> f("1")
'This is a string!'
>>> f(1)
'This is an integer!'
```
We haven't implemented a method for `float`s, so in that case an exception
will be raised:
```python
>>> f(1.0)
NotFoundLookupError: For function "f", signature Signature(builtins.float) could not be resolved.
```
Instead of implementing a method for `float`s, let's implement a method for
all numbers:
```python
from numbers import Number
@dispatch
def f(x: Number):
return "This is a number!"
```
Since a `float` is a `Number`, `f(1.0)` will return `"This is a number!"`.
But an `int` is also a `Number`, so `f(1)` can either return
`"This is an integer!"` or `"This is a number!"`.
The rule of multiple dispatch is that the *most specific* method is chosen:
```python
>>> f(1)
'This is an integer!'
```
since an `int` is a `Number`, but a `Number` is not necessarily an `int`.
For a function `f`, all available methods can be obtained with `f.methods`:
```python
>>> f.methods
{Signature(builtins.str): (<function __main__.f(x:str)>, builtins.object),
Signature(builtins.int): (<function __main__.f(x:int)>, builtins.object),
Signature(numbers.Number): (<function __main__.f(x:numbers.Number)>,
builtins.object)}
```
In the values, the first element in the tuple is the implementation and the
second element the return type.
For an excellent and way more detailed overview of multiple dispatch, see the
[manual of the Julia Language](https://docs.julialang.org/en/).
## Scope of Functions
Consider the following package design.
**package/\_\_init\_\_.py**
```python
import a
import b
```
**package/a.py**
```python
from plum import dispatch
@dispatch
def f(x: int):
return "int"
```
**package/b.py**
```python
from plum import dispatch
@dispatch
def f(x: float):
return "float"
```
In a design like this, the methods for `f` recorded by `dispatch` are _global_:
```python
>>> from package.a import f
>>> f(1.0)
'float'
```
This could be what you want, but it can also be undesirable, because it means that
someone could accidentally overwrite your methods.
To keep your functions private, you can create new dispatchers:
**package/\_\_init\_\_.py**
```python
import a
import b
```
**package/a.py**
```python
from plum import Dispatcher
dispatch = Dispatcher()
@dispatch
def f(x: int):
return "int"
```
**package/b.py**
```python
from plum import Dispatcher
dispatch = Dispatcher()
@dispatch
def f(x: float):
return "float"
```
```python
>>> from package.a import f
>>> f(1)
'int'
>>> f(1.0)
NotFoundLookupError: For function "f", signature Signature(builtins.float) could not be resolved.
>>> from package.b import f
>>> f(1)
NotFoundLookupError: For function "f", signature Signature(builtins.int) could not be resolved.
>>> f(1.0)
'float'
```
## Classes
You can use dispatch within classes:
```python
from plum import dispatch
class Real:
@dispatch
def __add__(self, other: int):
return "int added"
@dispatch
def __add__(self, other: float):
return "float added"
```
```python
>>> real = Real()
>>> real + 1
'int added'
>>> real + 1.0
'float added'
```
If you use other decorators, then `dispatch` must be the _outermost_ decorator:
```python
class Real:
@dispatch
@decorator
def __add__(self, other: int):
return "int added"
```
### Forward References
Imagine the following design:
```python
from plum import dispatch
class Real:
@dispatch
def __add__(self, other: Real):
pass # Do something here.
```
If we try to run this, we get the following error:
```python
NameError Traceback (most recent call last)
<ipython-input-1-2c6fe56c8a98> in <module>
1 from plum import dispatch
2
----> 3 class Real:
4 @dispatch
5 def __add__(self, other: Real):
<ipython-input-1-2c6fe56c8a98> in Real()
3 class Real:
4 @dispatch
----> 5 def __add__(self, other: Real):
6 pass # Do something here.
NameError: name 'Real' is not defined
```
The problem is that name `Real` is not yet defined, when `__add__` is defined and
the type hint for `other` is set.
To circumvent this issue, you can use a forward reference:
```python
from plum import dispatch
class Real:
@dispatch
def __add__(self, other: "Real"):
pass # Do something here.
```
**Note:**
A forward reference `"A"` will resolve to the _next defined_ class `A` _in
which dispatch is used_.
This works fine for self references.
In is recommended to only use forward references for self references.
For more advanced use cases of forward references, you can use `plum.type.PromisedType`.
## Keyword Arguments and Default Values
Default arguments can be used.
The type annotation must match the default value otherwise an error is thrown.
As the example below illustrates, different default values can be used for different methods:
```python
from plum import dispatch
@dispatch
def f(x: int, y: int = 3):
return y
@dispatch
def f(x: float, y: float = 3.0):
return y
```
```python
>>> f(1)
3
>>> f(1.0)
3.0
>>> f(1.0, 4.0)
4.0
>>> f(1.0, y=4.0)
4.0
```
Keyword-only arguments, separated by an asterisk from the other arguments, can
also be used, but are *not* dispatched on.
Example:
```python
from plum import dispatch
@dispatch
def f(x, *, option="a"):
return option
```
```python
>>> f(1)
'a'
>>> f(1, option="b")
'b'
>>> f(1, "b") # This will not work, because `option` must be given as a keyword.
NotFoundLookupError: For function "f", signature Signature(builtins.int, builtins.str) could not be resolved.
```
## Comparison with `multipledispatch`
As an alternative to Plum, there is
[multipledispatch](https://github.com/mrocklin/multipledispatch), which also is a
great solu