DSL design
ORM::Factory's DSL needs to capture arbitrary attribute names inside a
block without pre-declaring them. Raku's identifier resolution is lexical
and compile-time; arbitrary bare-identifier calls cannot resolve to
runtime-discovered names without either parser surgery or topic-driven
method dispatch. This page surveys the candidate mechanisms and records
the chosen design.
Candidate mechanisms
1. Topic-$_ builder + FALLBACK
The block is run with $_ bound to a FactoryBuilder instance, so method
calls on the topic use the leading-dot shorthand. The builder defines
FALLBACK to capture any unknown method name as an attribute.
1 2 3 4 5 | |
- Pros: stays inside the standard language, dispatches at runtime,
composes with
:-positional sugar, lets us mix declared DSL methods (.variant,.transient,.association) with FALLBACK-captured attributes on the same object. - Cons: the leading
.is mandatory —fname 'Greg'would parse as a sub call, not a method, and Raku will not synthesize an unknown sub on demand. - Disambiguation: a
Callablearg means dynamic; everything else means static.add-attributeis the escape hatch for a literalCallablevalue or a name that collides with a DSL method.
2. Bare identifiers via exported sub stubs
Every attribute would have to be an exported sub. Since attributes are open sets, this requires either:
- a build step generating a sub per attribute, or
- a fallback sub that uses
&?ROUTINEintrospection.
Neither preserves the defined inside the block property: factory 'user'
would need to know each user-defined attribute name at compile time.
- Pros: zero leading-dot noise (
fname 'Greg'). - Cons: does not work for runtime-defined attribute names; collides
with built-in sub names (
given,when, …); pollutes the global sub namespace. Rejected.
3. &postcircumfix
postcircumfix:<{ }> and similar are operator overloads on the container,
not a way to capture an identifier call. Attribute access would degenerate
into <fname> = 'Greg' syntax (subscript-on-builder), which loses the
attribute-as-statement shape.
- Pros: none meaningful here.
- Cons: does not solve the identifier problem. Rejected.
4. Custom slang (use slang ...)
Define a sub-grammar that re-parses the inside of factory { ... } to turn
bare identifiers into builder method calls.
- Pros: can drop the leading dot:
fname 'Greg'. - Cons: slang code is non-trivial, fragile across Rakudo releases, hard
to debug (mis-tokenisation reports point into the slang), and bleeds into
editor / static-analysis tooling. Every later piece of the library pays an
ongoing tax. Rejected as default; revisit only if the topic-
$_shape proves unworkable in real specs.
5. EXPORTHOW / metamodel injection
EXPORTHOW swaps the meta-class for a unit. It can give a class a custom
declarator (e.g. factory user { ... }), but does not affect what
identifiers inside the block resolve to.
- Pros: could produce factory declarations that look like class declarations.
- Cons: does not solve the attribute-capture problem; pushes definitions
out of
define { ... }into top-level declarators, which fights the per-suite registry-reset model. Rejected.
Decision
Topic-$_ builder + FALLBACK (mechanism #1) is the chosen DSL.
| Concern | Resolution |
|---|---|
| Entry point | define { ... } runs the block with $_ bound to a DefinitionBuilder. |
factory, sequence, variant, transient, association, after, before, initialize-with, to-create, modify, skip-create |
Explicit methods on the relevant builder — declared, not FALLBACK'd, so typos surface as method-not-found. |
| Attribute capture | FALLBACK on FactoryBuilder, dispatching to add-attribute with the captured name and the call's arguments. |
| Static vs dynamic | A single Callable positional ⇒ dynamic block; anything else ⇒ static value. The disambiguation is exhaustive because attribute calls take at most one positional. |
| Escape hatch | add-attribute for names that collide with declared DSL methods, or to force a Callable literal as a static value. |
| Variant application inside a factory | .admin; resolves through FALLBACK. A registered variant of the matching name wins over attribute capture; missing-name errors mention both possibilities. |
Trade-off accepted
The user-visible cost is the leading . on every attribute, plus the :
between method and positional. Both are standard Raku — no slang, no
metamodel surgery. The win is that the DSL is just methods on an object,
which means it composes with Raku's existing tooling: introspection, ^methods,
does for behaviour mix-ins, and ordinary stack traces on failure.