mapdispatch
A minimalist library to bring Clojure-style multimethods, method combinations, and tag-based polymorphism into Python — without using classes.
Features
- Single and multiple argument dispatch
- Hierarchical dispatch using
deriveandisa - Extensible method registration with
defmethod - Compositional hooks with
:before,:after, and:around - Class-free, functional programming style
Installation
Just clone or copy the mapdispatch/ folder into your project.
# if you still use `pip`:
pip install mapdispatch
# but we highly recommend to use `uv`:
uv add mapdispatch
# and after installation via uv you can run the examples by:
mapdispatch-zoo
Quickstart
Define animals as dictionaries
simba = {"name": "Simba", "type": "lion"}
dumbo = {"name": "Dumbo", "type": "elephant"}
Create a hierarchy
from mapdispatch import Hierarchy, MultiMethod
h = Hierarchy()
h.derive("lion", "big-cat")
h.derive("big-cat", "animal")
h.derive("elephant", "animal")
Define a multimethod
feed = MultiMethod(lambda a: a["type"], hierarchy=h)
Register methods
def lion_feed(a):
print(f"{a['name']} eats meat")
def elephant_feed(a):
print(f"{a['name']} eats bananas")
feed.defmethod("lion", lion_feed)
feed.defmethod("elephant", elephant_feed)
Use it
feed(simba) # Simba eats meat
feed(dumbo) # Dumbo eats bananas
Real Multiple Dispatch
You can dispatch on multiple arguments by making the dispatch function return a tuple:
interact = MultiMethod(lambda a, b: (a["type"], b["type"]), hierarchy=h)
def lion_vs_elephant(a, b):
print(f"{a['name']} chases {b['name']}")
interact.defmethod(("lion", "elephant"), lion_vs_elephant)
interact(simba, dumbo) # Simba chases Dumbo
Method Combinations
:before – run logic before the main method
def open_cage(a):
print(f"Opening cage for {a['name']}")
feed.defmethod("lion", open_cage, kind=":before")
:after – run logic after the main method
def cleanup(a):
print(f"Cleaning up after {a['name']}")
feed.defmethod("lion", cleanup, kind=":after")
:around – wrap the primary method
def log_feed(inner_fn, a):
print(f"[LOG] Start {a['name']}")
result = inner_fn(a)
print(f"[LOG] End {a['name']}")
return result
feed.defmethod("lion", log_feed, kind=":around")
Method Registration via Decorators
You can also define methods using decorators instead of manual registration.
Primary method:
@feed.defmethod("lion")
def lion_feed(a):
print(f"{a['name']} eats meat")
With hooks:
@feed.defmethod("lion", kind=":before")
def open_cage(a):
print(f"Opening cage for {a['name']}")
@feed.defmethod("lion", kind=":after")
def cleanup(a):
print(f"Cleaning cage after {a['name']}")
@feed.defmethod("lion", kind=":around")
def logger(inner_fn, a):
print("[LOG] Start")
result = inner_fn(a)
print("[LOG] End")
return result
this is fully equivalent to manually registering via feed.defmethod(...) and is great for organizeing logic clearly and idiomatically.
Why mapdispatch?
This library helps you:
- Write open, extensible, polymorphic code
- Avoid deep class hierarchies
- Dispatch based on dynamic rules
- Cleanly separate data from behavior
Inspired by:
Example Output
Opening cage for Simba
[LOG] Start Simba
Simba eats meat
[LOG] End Simba
Cleaning up after Simba
Coming Soon
- Decorator-based syntax (
@defmethod) - Predicate-based dispatch
- Package on PyPI
- Integration with dataclasses or Pydantic (optional)
Philosophy
“Data is just data. Behavior is just behavior. Dispatch is glue.”
– Not Rich Hickey, but close enough.
Metadata
Release files for mapdispatch 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mapdispatch-0.1.1.tar.gz | 6.1 kB | Without provenance | Details for mapdispatch-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mapdispatch-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for mapdispatch-0.1.1-py3-none-any.whl |
Total release size: 12.5 kB
Release files / mapdispatch-0.1.1.tar.gz
| Download URL | mapdispatch-0.1.1.tar.gz |
|---|---|
| Size | 6.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3e1bd92c680e6b7ead5e7a9c71bd871dcc01c8d9c7fab6774ce48ba1ad213dde
|
|
BLAKE2b-256 checksum How to use checksums |
02b75cec1ebaf05b025c3ef8f597c7ca1e64a83fbfde6758015fb8490e395dc1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.4
|
Release files / mapdispatch-0.1.1-py3-none-any.whl
| Download URL | mapdispatch-0.1.1-py3-none-any.whl |
|---|---|
| Size | 6.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
af8a30238dd0993b00b5409ec499022722ef4811be7a7054c5651350d5f2bb79
|
|
BLAKE2b-256 checksum How to use checksums |
aee9fcb5ef521a91cfa67222ae16b8a8da62ef7c9d1e2f7f0dc77e251c4eed01
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.4
|
Security for mapdispatch 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.