# apfel.core.dispatch

*module*

Dynamic dispatch facilities for Python.

This addresses the lack of **runtime-available**, **inheritance-free** interfaces in Python.
It partially resembles the [:kotlin `extension`](https://kotlinlang.org/docs/extensions.html) and [:rust `trait`](https://doc.rust-lang.org/book/ch10-02-traits.html).

## Rationale

**Why not [:python `functools.singledispatch`](https://docs.python.org/3/library/functools.html#functools.singledispatch)?**

- Runtime behavior based on type hints can be confusing.
- Its method variant [:python `functools.singledispatchmethod`](https://docs.python.org/3/library/functools.html#functools.singledispatchmethod) dispatches methods based on the first non-`self` argument, which is very different from the single dispatch found in other object-oriented programming languages.

**Why not [:python `abc.ABC`](https://docs.python.org/3/library/abc.html#abc.ABC)?**

- It does not enable [:cpp `virtual`](https://cppreference.com/w/cpp/language/virtual)-like or [:rust `dyn`](https://doc.rust-lang.org/book/ch18-02-trait-objects.html)-like dispatch.
An [:python `abstractmethod`](https://docs.python.org/3/library/abc.html#abc.abstractmethod) in an abstract class will not forward the call to the concrete implementation based on the real type.
i.e., there is no facilities for implementation selection based on concrete types.
- Although [:python `ABCMeta.register`](https://docs.python.org/3/library/abc.html#abc.ABCMeta.register) can be used to register non-child classes, it cannot inject implementations to the registered classes.
Some classes do not allow monkey-patching and the implementations must be stored elsewhere, registering them to the ABC is logical error, as the implementations are not part of the concrete class.

Using [`ABCDispatch`][apfel.core.dispatch.ABCDispatch] defined in this module,

- All ABC features work out of the box. Supports static type checking (inheritance-based usages only), non-inheritance based runtime type checking, and `@abstractmethod`.
- Abstract methods defined in an `ABCDispatch` can **dynamically** forward the call to the concrete implementation based on the real type, and the implementations can be externally provided.
  For example, an `ABCDispatch` can be used to extend built-in classes.

## Usage

First, define a class that subclasses [`ABCDispatch`][apfel.core.dispatch.ABCDispatch].
This class will be semantically similar to [:rust Rust traits](https://doc.rust-lang.org/book/ch10-02-traits.html).
All interface functions defined in this method shall be decorated with [:python `@abc.abstractmethod`](https://docs.python.org/3/library/abc.html#abc.abstractmethod).
Class methods and static methods can also be defined with the same decorator.

> **Warning**
>
> The `@abstractmethod` decorator must be placed after the `@classmethod` or `@staticmethod` decorator.

```python
class Interface(ABCDispatch):
    @abstractmethod
    def method(self, f): ...

    @classmethod
    @abstractmethod
    def class_method(cls, f): ...

    @staticmethod
    @abstractmethod
    def static_method(f): ...
```

Then, we can implement the "interface" for a class by using the [`@impl`][apfel.core.dispatch.impl] decorator.
The implementor class is passed as the first argument to the decorator.
The implementation is written in the `class` syntax, with each interface method implemented as a method of the class.
The class name doesn't matter, but it should subclass the implementor class.
This syntax provides you the ability to reference concrete attributes and methods defined on the implementor class.

> **Warning**
>
> Note that some built-in classes are not allowed to be subclassed in Python.
> You may need to use the [`add_impl`][apfel.core.dispatch.add_impl] function to add implementations imperatively in such cases.

```python
@impl(Interface)
class _(Implementor):
    def method(self, f): """Concrete implementation goes here"""

    @classmethod
    def class_method(cls, f): """Concrete implementation goes here"""

    @staticmethod
    def static_method(f): """Concrete implementation goes here"""
```

In this case, `Implementor.method` does not exist during runtime so you may not call it, and calling `.method` on an instance of `Implementor` will also fail.
But you can call `Interface.method` on an instance of `Implementor` and it will dispatch to the concrete implementation defined in the `@impl` construct.

```python
c = Implementor()
Interface.method(c, f)  # Calls the implementation within the @impl
```

If you have control to the source code, you can add `Interface` to the base classes of `Implementor`,
so `Implementor.method(instance)` and `instance.method()` will work as expected, `Interface.method(instance, f)` shall also work.

Class methods and static methods shall select a concrete implementation explicitly, as the concrete class is not inferrable from the function's call arguments.

```python
Interface.dispatch(Implementor).class_method(f)
Interface.dispatch(Implementor).static_method(f)
```

The older generic syntax, e.g. `Interface.class_method[Implementor](f)`, is also supported.

It's also possible to define a dispatchable function using the [`@dispatched`][apfel.core.dispatch.dispatched] decorator.
Then a method `impl_for` will be on the function, which can be used to register implementations.

```python
@dispatched
def f(x): ...

@f.impl_for(int)
def _(x: int):
    """Implementation for int goes here"""
```

> **Example**
>
> You can check the [`Functor`][apfel.core.monad.Functor]'s source code as an example of how to use this module.

By default `ABCDispatch` and `@dispatched` defines a [single dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch#Single_dispatch).
This means the runtime implementation selection is based on **the concrete type** of **first argument** of the function, or of the **receiver** in the case of methods.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L1-L1)

## apfel.core.dispatch.ABCDispatch

*class*

```python
class ABCDispatch
```

An `ABC` that enables single dispatch for its abstract methods.
This behaves similarly to [:python `abc.ABC`](https://docs.python.org/3/library/abc.html#abc.ABC) but with the added feature of dynamic concrete implementation selection under single dispatch.
Stick to built-in [:python `abc.abstractmethod`](https://docs.python.org/3/library/abc.html#abc.abstractmethod) to define abstract methods.

See the [usage section](#usage) for more information.

```python
from abc import abstractmethod
from apfel.core.dispatch import ABCDispatch

class A(ABCDispatch):
    @abstractmethod
    def hi(self):
        print(self)

    @classmethod
    @abstractmethod
    def hey(cls):
        print(cls)

a: A = some_instance()
A.hi(a)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L226-L255)

### apfel.core.dispatch.ABCDispatch.__dispatch_methods__

*attribute* · *class attribute*

```python
__dispatch_methods__: set[str]
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L20-L20)

### apfel.core.dispatch.ABCDispatch.dispatch

*method* · *classmethod*

```python
def dispatch(implementor: type[T], /) -> Self
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L253-L255)

## apfel.core.dispatch.ABCDispatchMeta

*class*

```python
class ABCDispatchMeta(ABCMeta)
```

Bases: `ABCMeta`

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L127-L155)

### apfel.core.dispatch.ABCDispatchMeta.__new__

*method*

```python
def __new__(name, bases, namespace, **kwargs)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L128-L155)

## apfel.core.dispatch.ABCDispatchProxy

*class*

```python
class ABCDispatchProxy
```

A proxy for selecting implementations of an [`ABCDispatch`][apfel.core.dispatch.ABCDispatch].

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L158-L215)

### apfel.core.dispatch.ABCDispatchProxy.implementor

*attribute* · *instance attribute*

```python
implementor: type[T] = implementor if isinstance(implementor, type) else type(implementor)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L168-L170)

### apfel.core.dispatch.ABCDispatchProxy.interface

*attribute* · *instance attribute*

```python
interface: type[I] = interface
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L167-L167)

### apfel.core.dispatch.ABCDispatchProxy.__getattr__

*method*

```python
def __getattr__(name: str) -> Any
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L172-L193)

### apfel.core.dispatch.ABCDispatchProxy.__init__

*method*

```python
def __init__(interface: type[I], implementor: type[T], /) -> None
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L166-L170)

## apfel.core.dispatch.DispatchFunction

*class*

```python
class DispatchFunction
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L52-L56)

### apfel.core.dispatch.DispatchFunction.__dispatch__

*property*

```python
__dispatch__: DispatchRegistry[P, [type], R]
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L56-L56)

### apfel.core.dispatch.DispatchFunction.__call__

*method*

```python
def __call__(*args: P.args, **kwargs: P.kwargs) -> R
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L53-L53)

### apfel.core.dispatch.DispatchFunction.impl_for

*method*

```python
def impl_for(cls: type) -> Callable[[Callable[P, R]], Callable[P, R]]
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L54-L54)

## apfel.core.dispatch.DispatchRegistry

*class*

```python
class DispatchRegistry(IDispatchRegistry)
```

Bases: `apfel.core.dispatch.IDispatchRegistry`

Default [dispatch registry][apfel.core.dispatch.IDispatchRegistry] using single dispatch.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L336-L419)

### apfel.core.dispatch.DispatchRegistry.decorate_assignments

*attribute* · *instance attribute*

```python
decorate_assignments: Sequence[str] = decorate_assignments or WRAPPER_ASSIGNMENTS
```

What attributes to copy from the original function to the decorated function
when calling the `make_dispatch_func` method.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L367-L367)

### apfel.core.dispatch.DispatchRegistry.enclosing_class

*attribute* · *instance attribute*

```python
enclosing_class: type | None = enclosing_class
```

The class that the dispatched function is a method of.
If it is not a method, this should be `None`.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L366-L366)

### apfel.core.dispatch.DispatchRegistry.function

*attribute* · *instance attribute*

```python
function: Callable[P, R] = func or unimplemented
```

The fallback function or method.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L364-L364)

### apfel.core.dispatch.DispatchRegistry.registry

*attribute* · *instance attribute*

```python
registry: dict[type, Callable[P, R]] = {}
```

A mapping from types to functions or methods.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L365-L365)

### apfel.core.dispatch.DispatchRegistry.__init__

*method*

```python
def __init__(func: Callable[P, R] | None = None, *, enclosing_class: type | None = None, decorate_assignments: Sequence[str] | None = None) -> None
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L363-L367)

### apfel.core.dispatch.DispatchRegistry.add_impl

*method*

```python
def add_impl(func, *args, **kwargs)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L403-L410)

### apfel.core.dispatch.DispatchRegistry.decide_impl

*method*

```python
def decide_impl(*args, **kwargs)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L369-L390)

### apfel.core.dispatch.DispatchRegistry.get_impl

*method*

```python
def get_impl(*args, **kwargs)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L412-L419)

### apfel.core.dispatch.DispatchRegistry.make_dispatch_func

*method*

```python
def make_dispatch_func(func)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L392-L401)

## apfel.core.dispatch.DispatchRegistryForClassMethod

*class*

```python
class DispatchRegistryForClassMethod(DispatchRegistry)
```

Bases: `apfel.core.dispatch.DispatchRegistry`

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L422-L476)

### apfel.core.dispatch.DispatchRegistryForClassMethod.add_impl

*method*

```python
def add_impl(func, *args, **kwargs)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L473-L476)

### apfel.core.dispatch.DispatchRegistryForClassMethod.decide_impl

*method*

```python
def decide_impl(*args, **kwargs)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L423-L450)

### apfel.core.dispatch.DispatchRegistryForClassMethod.make_dispatch_func

*method*

```python
def make_dispatch_func(func)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L452-L471)

## apfel.core.dispatch.DispatchRegistryForStaticMethod

*class*

```python
class DispatchRegistryForStaticMethod(DispatchRegistryForClassMethod)
```

Bases: `apfel.core.dispatch.DispatchRegistryForClassMethod`

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L479-L497)

### apfel.core.dispatch.DispatchRegistryForStaticMethod.add_impl

*method*

```python
def add_impl(func, *args, **kwargs)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L494-L497)

### apfel.core.dispatch.DispatchRegistryForStaticMethod.make_dispatch_func

*method*

```python
def make_dispatch_func(func)
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L480-L492)

## apfel.core.dispatch.IABCDispatch

*class*

```python
class IABCDispatch(Protocol)
```

Bases: `Protocol`

A protocol for ABCs that supports dispatching regardless of dispatch flavor.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L218-L223)

### apfel.core.dispatch.IABCDispatch.__dispatch_methods__

*attribute* · *class attribute*

```python
__dispatch_methods__: set[str]
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L223-L223)

## apfel.core.dispatch.IDispatchRegistry

*class*

```python
class IDispatchRegistry(Protocol)
```

Bases: `Protocol`

A protocol for dispatch registry.
A dispatch registry is a collection of implementations of a
polymorphic function or method.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L258-L333)

### apfel.core.dispatch.IDispatchRegistry.add_impl

*method*

```python
def add_impl(func: Callable[P, R], *args: K.args, **kwargs: K.kwargs) -> None
```

```python
def add_impl(self, func: Callable[P, R], *args: K.args, **kwargs: K.kwargs)
```
Add a new implementation to the registry.

**Parameters**

- `func` (`Callable[P, R]`) — The implementation to be added.
- `...` (`K`) — The implementor of the implementation added.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L304-L315)

### apfel.core.dispatch.IDispatchRegistry.decide_impl

*method*

```python
def decide_impl(*args: P.args, **kwargs: P.kwargs) -> Callable[P, R]
```

```python
def decide_impl(self, *args: P.args, **kwargs: R.args) -> Callable[P, R]
```
Decide which implementation should be used based on
the arguments.

This method should not call the implementation directly,
but return a reference to it.

**Returns**

- `Callable` (`Callable[P, R]`) — The implementation to be used.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L265-L279)

### apfel.core.dispatch.IDispatchRegistry.get_impl

*method*

```python
def get_impl(*args: K.args, **kwargs: K.kwargs) -> Callable[P, R]
```

```python
def get_impl(self, *args: K.args, **kwargs: K.kwargs) -> Callable raise KeyError
```
Get an implementation from the registry.

**Parameters**

- `...` (`K`) — The implementor of the implementation.

**Returns**

- (`Callable[P, R]`) — Callable[Self.P, Self.R]: The implementation.

**Raises**

- `KeyError` — If no such implementation is found.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L317-L333)

### apfel.core.dispatch.IDispatchRegistry.make_dispatch_func

*method*

```python
def make_dispatch_func(func: Callable[P, R]) -> Callable[P, R]
```

```python
def make_dispatch_func(self, func: Callable[P, R]) -> Callable[P, R]
```
Make a function or callable that uses this registry to perform dynamic dispatch.

> **Note**
>
> This method should typically be implemented as a higher-order function,
> which returns a new function that wraps the original function with the
> dispatch logic.
> An example implementation of this method:
> ```python
> def make_dispatch_func(self, func):
>     def dyn(*args, **kwargs):
>         return self.decide_impl(*args, **kwargs)(*args, **kwargs)
> 
>     functools.update_wrapper(dyn, func)
>     return dyn
> ```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L281-L302)

## apfel.core.dispatch.add_impl

*function*

```python
def add_impl(interface: IABCDispatch, implementation: Mapping[str, Callable], *impl_for_args, **impl_for_kwargs) -> None
```

An imperative interface for adding implementations to a dispatchable interface.
For a declarative interface, use the [`@impl`][apfel.core.dispatch.impl] decorator.

If the dispatchable interface is an [`ABCDispatch`][apfel.core.dispatch.ABCDispatch] which does single dispatch,
the `impl_for_args` param should be the type of the implementor class.

```python
# The following imperative code ...
add_impl(Interface, {"method": lambda self, f: f}, Implementor)

# ... is equivalent to the following declarative code:
@impl(Interface)
class _(Implementor):
    def method(self, f): return f
```

**Parameters**

- `interface` (`IABCDispatch`) — The dispatchable interface.
- `implementation` (`Mapping[str, Callable]`) — A mapping from method names to implementations.
- `*impl_for_args` (`Any`) (default: `()`) — Arguments that the dispatch mechanism will use for selecting the implementation.
- `**impl_for_kwargs` (`Any`) (default: `{}`) — Keyword arguments that the dispatch mechanism will use for selecting the implementation.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L619-L646)

## apfel.core.dispatch.dispatch

*function*

```python
def dispatch(func: Callable[P, R], /) -> DispatchFunction[P, R]
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L59-L59)

## apfel.core.dispatch.dispatched

*function*

```python
def dispatched(func: Callable[P, R], /) -> DispatchFunction[P, R]
```

A decorator for creating a single-dispatchable function.
It will add an `impl_for` method to the function, which can be used to register implementations.
Calling the function will dispatch to the correct implementation based on the type of the first argument.

This is similar to [:python `functools.singledispatch`](https://docs.python.org/3/library/functools.html#functools.singledispatch), but does not use type hints for dispatching, and static type unions are not supported.

```python
@dispatched
def show(x):
    ...

@show.impl_for(int)
def _(x: int):
    return f"int: {x}"

@show.impl_for(str)
def _(x: str):
    return f"str: {x}"

show(1)       # "int: 1"
show("hello") # "str: hello"
```

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L500-L541)

## apfel.core.dispatch.impl

*function*

```python
def impl(interface: IABCDispatch, /) -> Callable[[type[T]], type[T]]
```

Decorator for registering an implementation using the `class` syntax.

See the [usage section](#usage) for more information.

Here `Implementor` is a concrete class and `Interface` is an [`ABCDispatch`][apfel.core.dispatch.ABCDispatch].

```python
@impl(Interface)
class _(Implementor):
    def method(self, f): """Concrete implementation goes here"""

    @classmethod
    def class_method(cls, f): """Concrete implementation goes here"""

    @staticmethod
    def static_method(f): """Concrete implementation goes here"""
```

**Parameters**

- `interface` (`IABCDispatch`) — The dispatchable interface.

[View source](https://github.com/Yixuan-Wang/apfel/blob/main/src/apfel/core/dispatch.py#L550-L616)
