Skip to main content

Clojure-style classless map-based multimethods and dispatch system for Python

Project description

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 derive and isa
  • 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.


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mapdispatch-0.1.1.tar.gz (6.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mapdispatch-0.1.1-py3-none-any.whl (6.4 kB view details)

Uploaded Python 3

File details

Details for the file mapdispatch-0.1.1.tar.gz.

File metadata

  • Download URL: mapdispatch-0.1.1.tar.gz
  • Upload date:
  • Size: 6.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.4

File hashes

Hashes for mapdispatch-0.1.1.tar.gz
Algorithm Hash digest
SHA256 3e1bd92c680e6b7ead5e7a9c71bd871dcc01c8d9c7fab6774ce48ba1ad213dde
MD5 b37556cddf1111761a6566620c764751
BLAKE2b-256 02b75cec1ebaf05b025c3ef8f597c7ca1e64a83fbfde6758015fb8490e395dc1

See more details on using hashes here.

File details

Details for the file mapdispatch-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for mapdispatch-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 af8a30238dd0993b00b5409ec499022722ef4811be7a7054c5651350d5f2bb79
MD5 5382f0bc46900e10a5e21cd18438de72
BLAKE2b-256 aee9fcb5ef521a91cfa67222ae16b8a8da62ef7c9d1e2f7f0dc77e251c4eed01

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page