Status
Accepted
Context
Slug encourages clear, explicit code that is easy to read, reason about, and refactor. As Slug codebases grew, a recurring need emerged for readable function chaining and left-to-right data flow, especially when composing small, reusable functions.
Using . for chaining (e.g. "foo".len().println()) introduced several issues:
- Ambiguity between field access, function calls, and future namespaces.
- Unintended behavior changes when maps contain functions with the same names as globals.
- Poor readability when namespaces and chaining mix (e.g.
9.math.double().println()). - Increased parser and evaluator complexity for what is fundamentally function composition.
Slug needed a way to express value flow without overloading . or introducing implicit behavior.
Decision
Slug introduces a new operator: the trail operator (/>).
The trail operator expresses left-to-right function application, where the value on the left is passed as the first argument to the expression on the right.
a /> f
is equivalent to:
f(a)
Chained trails associate left-to-right:
a /> f /> g
is equivalent to:
g(f(a))
The operator is purely syntactic sugar and does not introduce new runtime semantics.
Examples
10 /> double /> println("is 20")
[1, 2, 3]
/> sum
/> println("total")
10 /> map.double /> lst[1] /> println("is 40")
These examples read in execution order, matching how values move through the program.
Semantics
/>is a binary operator.- The left-hand side is evaluated first.
- The result is passed as the first argument to the right-hand expression.
- The right-hand side must evaluate to a callable expression.
- Additional arguments on the right-hand side are preserved.
Example:
x /> f(a, b)
desugars to:
f(x, a, b)
Desugaring
The trail operator is desugared during parsing or an early AST-lowering phase.
Example AST transformation:
a /> f /> g
↓
g(f(a))
No special handling is required in the evaluator beyond standard function calls.
Design Rationale
Why not . chaining?
.implies ownership, fields, or methods.- Slug currently has only maps and functions.
- Overloading
.introduced subtle and surprising behavior changes. - Future features (modules, structs, namespaces) would further complicate semantics.
Why not |?
- Conflicts with bitwise OR.
- Creates ambiguity with logical operators.
- Harder to parse cleanly in expressions.
Why />?
- Visually conveys flow and motion.
- Easy to type.
- Does not conflict with existing operators.
- Reads naturally left-to-right.
- Distinctive and idiomatic to Slug.
- Avoids semantic overloading of existing syntax.
The operator also aligns aesthetically with Slug’s identity: a clear trail of transformations, rather than nested calls or object-style chaining.
Consequences
Positive
- Greatly improves readability of composed function calls.
- Eliminates ambiguity between chaining, lookup, and invocation.
- Keeps the evaluator and runtime model simple.
- Works uniformly with global functions, map lookups, and future constructs.
- Encourages a functional, compositional style consistent with Slug’s philosophy.
Negative
- Introduces a new operator users must learn.
- Some users may initially look for
.-style chaining out of habit.
Neutral
/>is optional; traditional function calls remain fully supported.- The operator is syntax-only and does not affect runtime performance.
- Future language features (modules, structs, namespaces) can be added without redefining
/>.
Notes
The trail operator is intentionally limited to value flow, not function composition. Future operators (e.g. function-to-function composition) may be introduced separately if a clear use case emerges.