# How Quarkdown Function Declaration Works with Lambda Parameters

> Discover how Quarkdown's .function declaration compiles into SimpleFunction objects, executing Lambda bodies by binding arguments as callable functions in a temporary library.

- Repository: [Giorgio Garofalo/quarkdown](https://github.com/iamgio/quarkdown)
- Tags: internals
- Published: 2026-04-29

---

**Quarkdown compiles `.function` blocks into native `SimpleFunction` objects that execute `Lambda` bodies by binding arguments as callable functions in a temporary `__lambda-parameters__` library, enabling both explicit named parameters and implicit positional access.**

The `iamgio/quarkdown` repository implements a sophisticated macro system where Markdown documents define reusable procedures through the `.function` declaration. Understanding how Quarkdown function declaration lambda parameters operate requires examining the compiler's three-stage transformation from syntax parsing to runtime execution, as implemented in the Kotlin source of the stdlib and core modules.

## Stage 1: Declaration and Parameter Extraction

When the parser encounters a `.function` block, the declaration process begins in `Flow.function` within [`quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Flow.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Flow.kt). Lines 99-101 extract **explicit parameters** from the lambda body—those defined with syntax like `a b:` at the start of the function block.

These parameters are read from `Lambda.explicitParameters` and converted into `FunctionParameter` instances of type `DynamicValue`. The `@LikelyBody` annotation on the `body` parameter signals to the compiler that the content should be parsed as a `Lambda` object rather than plain text.

At this stage, the system distinguishes between:
- **Explicit parameters**: Named arguments declared at the function head (e.g., `a b:`)
- **Implicit parameters**: Positional arguments accessed via `.1`, `.2`, etc., when no explicit names are provided

## Stage 2: Runtime Context Creation and Argument Binding

When a custom function is invoked, the stored `Lambda` executes through `Lambda.invokeDynamic` in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/function/value/data/Lambda.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/function/value/data/Lambda.kt). Lines 42-56 in the `createLambdaParametersLibrary` method handle the critical step of making arguments available inside the function body.

The runtime creates a **forked context**—a new isolated scope that inherits from the calling environment. It then registers arguments as zero-argument functions within a temporary library named `__lambda-parameters__`. This architectural choice means that inside the function body, parameters are accessed as regular function calls like `.a` or `.b` rather than simple variables.

The key operations in this stage include:
- Creating the lambda-parameter library via `createLambdaParametersLibrary`
- Mapping explicit parameter names to their argument values
- Auto-generating positional accessors (`.1`, `.2`) when `explicitParameters.isEmpty()`
- Preparing the context for destructuring if the argument count and type allow it

## Stage 3: Execution and Argument Resolution

The final stage occurs within `Lambda.invokeDynamic` (lines 84-95 and 100-115), where the system validates inputs and executes the lambda body. This method performs several critical validation steps:

1. **Argument count validation**: Checks if the provided arguments match the expected parameters, filling missing optional arguments with `NoneValue`
2. **Destructuring**: If enabled and applicable, splits dictionary or pair arguments into multiple explicit parameters (lines 90-94)
3. **Library propagation**: Merges the calling context's libraries into the forked context after registering lambda parameters, ensuring outer variables remain accessible but can be shadowed by parameters

The lambda's **action** then runs within this prepared context, returning an `OutputValue` that the pipeline treats as a dynamic value for further processing or rendering.

## Working with Lambda Parameter Types

### Explicit Parameters

Explicit parameters provide named access to arguments. When you declare:

```markdown
.function {add}
    a b:
    .a::plus { .b }

```

The `Flow.function` parser identifies `a` and `b` as explicit parameters. During invocation of `.add {2} {3}`, `invokeDynamic` registers `.a` → `2` and `.b` → `3` in the lambda context, allowing the body to reference them as callable functions.

### Implicit Parameters

For functions without explicit parameter declarations, Quarkdown automatically creates positional accessors:

```markdown
.function {double}
    .1:
    .1::multiply by:{2}

```

Here, `explicitParameters.isEmpty()` triggers `createLambdaParametersLibrary` (line 48) to generate `.1`, `.2`, etc., based on argument position. Calling `.double {5}` binds `5` to `.1`.

### Optional Parameters

Parameters declared with a `?` suffix are optional. The system marks the `LambdaParameter` with `isOptional = true`, and `invokeDynamic` (lines 107-110) automatically supplies `NoneValue` for missing arguments:

```markdown
.function {greet}
    name?:
    **Hello .name::otherwise {World}**

```

If called without arguments, `.name` resolves to `none`, and `.name::otherwise {World}` yields "World".

## Advanced Features: Destructuring and Scope Propagation

### Argument Destructuring

Quarkdown supports destructuring single arguments into multiple parameters when the argument implements `Destructurable`:

```markdown
.function {show}
    key value:
    .key → .value

```

When called as `.show { {k: "foo", v: "bar"} }`, `invokeDynamic` recognizes the dictionary can be destructured into two parts to satisfy the two explicit parameters (lines 90-94).

### Outer Scope Access

The context forking mechanism preserves access to outer variables. Consider:

```markdown
.var {prefix} {Mr.}
.function {fullname}
    name:
    .prefix .name

```

The `callingContext.libraries` are copied into the lambda's context after parameter registration (lines 26-32 in the initialization flow), ensuring `.prefix` resolves correctly while allowing the parameter `.name` to shadow any conflicting outer definitions.

## Summary

- **Declaration Phase**: `Flow.function` in [`Flow.kt`](https://github.com/iamgio/quarkdown/blob/main/Flow.kt) parses `.function` bodies as `Lambda` objects, extracting explicit parameters from `Lambda.explicitParameters` and creating `FunctionParameter` definitions.
- **Runtime Binding**: `Lambda.invokeDynamic` creates a forked execution context and `createLambdaParametersLibrary` registers arguments as callable functions in the `__lambda-parameters__` library.
- **Access Patterns**: Parameters are accessible as explicit names (`.a`, `.b`) or implicit positions (`.1`, `.2`), with optional parameters defaulting to `none` when omitted.
- **Scope Management**: The runtime propagates calling context libraries while ensuring parameters shadow outer variables, and supports destructuring complex arguments into multiple parameters.

## Frequently Asked Questions

### How do explicit and implicit lambda parameters differ in Quarkdown?

**Explicit parameters** are declared with names at the start of a `.function` block (e.g., `a b:`) and accessed within the body as `.a` and `.b`. **Implicit parameters** are used when no explicit declaration exists; the system automatically creates positional accessors `.1`, `.2`, etc., corresponding to the argument order. Both types are implemented via the `createLambdaParametersLibrary` method in [`Lambda.kt`](https://github.com/iamgio/quarkdown/blob/main/Lambda.kt), but explicit parameters are extracted during parsing in [`Flow.kt`](https://github.com/iamgio/quarkdown/blob/main/Flow.kt) (lines 99-101) while implicit ones are generated at runtime (line 48).

### What happens when a Quarkdown function is called with missing optional arguments?

When a parameter is declared with the `?` suffix (making it optional), the `Lambda.invokeDynamic` method detects the missing argument during the validation phase (lines 107-110) and automatically fills it with `NoneValue`. Inside the function body, this `none` value can be handled using functions like `.name::otherwise {default}` to provide fallback behavior, or it will propagate as `none` through subsequent operations.

### Can Quarkdown functions access variables defined in outer scopes?

Yes. When a lambda executes, `invokeDynamic` copies the `callingContext.libraries` into the new forked context after registering the lambda's own parameters. This propagation occurs in the initialization logic (referenced in lines 26-32 of the context setup), ensuring that variables from enclosing scopes remain visible. However, function parameters take precedence and will shadow any outer variables with conflicting names due to the library merge order.

### How does parameter destructuring work in Quarkdown function declarations?

Destructuring occurs in `Lambda.invokeDynamic` (lines 90-94) when a single argument implements the `Destructurable` interface and the function expects multiple explicit parameters. The system splits the complex argument—such as a dictionary or pair—into constituent parts, mapping each part to the corresponding explicit parameter. This allows a function declared with `key value:` parameters to accept a single dictionary argument and automatically extract the values into the respective parameters.