# What Are Selectors and How Does Dispatch Work in Maru?

> Understand Maru selectors and dispatch. Learn how Maru implements polymorphism using argument types method tables and method fallback for efficient code execution.

- Repository: [Attila Lendvai/maru](https://github.com/attila-lendvai/maru)
- Tags: deep-dive
- Published: 2026-02-25

---

**In the Maru programming language, selectors implement single-dispatch polymorphism by using the first argument's type to index into a method table, with automatic fallback to superclass methods and default handlers when specific implementations are missing.**

Maru is a self-hosting Lisp dialect developed by Attila Lendvai that emphasizes minimalism and extensibility. At the heart of its object system lies the **selector**, a mechanism that enables dynamic method dispatch based on runtime types. Understanding how selectors and dispatch work in Maru is essential for extending the language and building efficient, polymorphic abstractions.

## Understanding Selectors in Maru

A **selector** in Maru is a first-class object that manages a collection of methods indexed by type. Unlike generic functions that might dispatch on multiple arguments, selectors perform **single-dispatch**—they choose an implementation based solely on the type of the first argument.

### The Selector Record Structure

The selector abstraction is defined as a record type `<selector>` in `source/selector.l` (lines 10-15). According to the Maru source code, this record contains three critical fields:

- **`name`** – A symbolic identifier for the selector (e.g., `+`, `print`, `eval`)
- **`methods`** – An expandable array where each index corresponds to a **type-id**; slots contain the specific method implementation for that type
- **`default`** – A fallback function invoked when no specific method or inherited method exists for a given type

This structure enables O(1) lookup for directly implemented methods while supporting inheritance through the fallback chain.

## How Dispatch Works in Maru

When you invoke a selector in Maru, the system executes a specific dispatch algorithm defined in the `apply` method for `<selector>` objects (`source/selector.l`, lines 26-40).

### The Dispatch Algorithm

The dispatch process follows these precise steps as implemented in the Maru evaluator:

1. **Extract the first argument** using `car` on the argument list
2. **Compute the type identifier**:
   - During bootstrapping, Maru uses `type-id-of` for fast lookup
   - In normal operation, it retrieves the full type-id via `<type>-id` applied to `(type-of …)`
3. **Array lookup** – The system calls `array-at` to fetch the method stored at the computed type-id index in the selector's `methods` array
4. **Hierarchy traversal** – If the array slot is empty, Maru invokes `selector/lookup-effective-method` (lines 16-24) which walks the type hierarchy using `<record>-super` links to find an inherited method
5. **Default fallback** – If no method exists in the hierarchy, the selector's `default` function is applied
6. **Execution** – The selected method (or default) is finally applied to the original argument list

Thus, the complete dispatch path is: **direct array slot → superclass walk → default handler**.

### Method Lookup and Inheritance

The `selector/lookup-effective-method` function enables polymorphic behavior by supporting inheritance. When a specific type lacks a method implementation, Maru traverses the superclass chain via the `<record>-super` field until it finds a matching method or exhausts the hierarchy.

This design allows developers to define methods on base types (like `<object>`) that automatically apply to derived types unless overridden, following standard object-oriented inheritance patterns.

## Defining and Adding Methods

Maru provides macros that simplify selector creation and method registration, all defined in `source/selector.l`.

### Creating Selectors with define-selector

The `define-selector` macro (lines 57-68) creates a fresh `<selector>` record with the specified name and an empty method table. If invoked with a name that already exists, it ensures the selector object is properly initialized.

### Adding Methods with define-method

Methods are registered using `selector/add-method` (lines 47-55), typically invoked through the `define-method` macro. The syntax follows:

```lisp
(define-method <selector> <type> (arguments…) body…)

```

This expands to a call that stores the method implementation in the selector's method array at the index corresponding to `<type>`'s type-id.

### Practical Example

Here is a complete example demonstrating selector definition and dispatch:

```lisp
;; Define a selector for serialization
(define-selector serialize)

;; Method for integers
(define-method serialize <integer> (n)
  (integer-to-string n))

;; Method for lists
(define-method serialize <list> (lst)
  (list "[" (join (map serialize lst) ", ") "]"))

;; Default method for unsupported types
(define-method serialize <object> (obj)
  (error "Cannot serialize object of type" (type-of obj)))

;; Usage
(serialize 42)           ; → "42"
(serialize '(1 2 3))     ; → "[1, 2, 3]"

```

## Selectors vs. Multimethods in Maru

While selectors provide single-dispatch polymorphism, Maru also supports **multimethods** through `source/generic.l`. Multimethods dispatch based on the types of **all** arguments rather than just the first, enabling multiple-dispatch polymorphism similar to CLOS (Common Lisp Object System).

Selectors remain the foundational mechanism used throughout Maru's core runtime for built-in operations like printing, arithmetic, and collection access, while multimethods offer additional flexibility for complex polymorphic scenarios.

## Summary

- **Selectors** in Maru provide single-dispatch polymorphism based on the first argument's type, implemented as `<selector>` records in `source/selector.l`.
- The **dispatch mechanism** uses a type-indexed array for O(1) lookup, falling back to superclass traversal via `selector/lookup-effective-method` and finally to a default handler.
- **Method registration** occurs through `define-method`, which expands to `selector/add-method` to populate the dispatch table.
- **Inheritance** is supported through the `<record>-super` chain, allowing methods defined on base types to apply to derived types.
- **Multimethods** in `source/generic.l` offer multiple-dispatch as an alternative to single-dispatch selectors.

## Frequently Asked Questions

### What is the difference between a selector and a generic function in Maru?

Selectors perform single-dispatch on the first argument only, while generic functions (defined in `source/generic.l`) support multiple-dispatch by considering the types of all arguments. Selectors are the foundational mechanism used throughout Maru's core runtime for built-in operations, whereas generic functions provide CLOS-style multiple-dispatch for complex polymorphic scenarios.

### How does Maru handle method lookup when a type has no direct implementation?

When a selector's method array lacks an entry for a specific type-id, Maru invokes `selector/lookup-effective-method` (defined in `source/selector.l`, lines 16-24) to traverse the type hierarchy. This function follows `<record>-super` links to check ancestor types for method implementations, enabling inheritance of methods from parent types. Only if the entire hierarchy is exhausted does the selector invoke its default handler.

### Can selectors be modified at runtime in Maru?

Yes, selectors are mutable records with expandable method arrays. You can dynamically add or replace methods using `selector/add-method` or the `define-method` macro without recompiling the selector definition. This runtime extensibility allows programs to extend the behavior of existing selectors for new types, supporting a flexible, open-system design pattern.

### Where is the selector dispatch logic implemented in the Maru source code?

The core dispatch algorithm resides in `source/selector.l` between lines 26-40, specifically within the `apply` method defined for `<selector>` records. The type hierarchy traversal for inherited methods is handled by `selector/lookup-effective-method` at lines 16-24 in the same file, while method registration occurs through `selector/add-method` at lines 47-55.