Pipes
A pipe is a self-operator that transforms a value in place. The colon
notation receiver:name(args) looks up the function name in the pipe table
and calls it with the receiver injected as the first argument:
receiver:name(args) == receiver = name(receiver, args)
Two important properties:
- The result is assigned back to the receiver, so pipes read naturally as
in-place transformations:s:trim():upper()trims then uppercasess. - The return type must match the receiver’s type. A pipe that returns a
different type raises aTYPE_ERROR.
Pipes resolve through the pipe table and the global/std scope, never
through the receiver’s own members — a function stored in an object is called
with dot (obj.fn(...), where the receiver is not passed).
var s = " hello "
s:trim():upper() # s -> "HELLO"
var arr = [3, 1, 2]
arr:sort():reverse() # arr -> [3, 2, 1]
var obj = {a: 1}
obj:merge({b: 2}) # obj -> {a: 1, b: 2}
print("abc":upper()) # error: literals cannot be receivers — bind to a name first
Conversion pipes
int, long, float, double, and to_string are pipes as well as
globals — the same function either way (x:int() is int(x)). They are the
deliberate exception to the same-type rule: a conversion rewrites the
receiver in place, changing its type:
var flt = 1.04
flt:int() # flt -> 1 (int)
var s = "3.7"
var i = s:double():int() # i -> 3; s itself became 3.7 along the way
var n = 42
n:to_string() # n -> "42"
A conversion that cannot be performed ("abc":int()) throws
INVALID_CONVERSION and leaves the receiver untouched.
The pipe builtins
pipe(name, fn) registers a callable name in the pipe table. From then on,
receiver:name(args) calls fn(receiver, args).
pipe_remove(name) unregisters it and returns true if it was removed:
pipe("twice", fn(n) { return n * 2 })
var x = 21
x:twice() # x -> 42
print(pipe_remove("twice")) # true
# x:twice() # error: unknown pipe 'twice'
Initially available pipes
The built-in pipe table is seeded with a curated set of standard library
functions:
Strings
upper lower trim trim_left trim_right
substr replace replace_first repeat pad_left pad_right
Arrays (from std.sort and std.seq)
sort sort_by shuffle unique flatten slice concat fill
map filter find_all zip
take drop chunk enumerate
set_union set_intersect set_difference
Objects
merge
Dispatch pipes — work on more than one receiver type:
insert # strings, arrays, objects
erase # strings, arrays, objects
reverse # strings, arrays
Conversion pipes — change the receiver’s type by design (see above):
int long float double to_string
var s = "hello"
s:insert(1, "X") # s -> "hXello"
var n = [1, 2, 3]
n:erase(1) # n -> [1, 3]
var w = "abc"
w:reverse() # w -> "cba"
Not every std.* function is available as a pipe — only the ones seeded above
(plus anything you register). To call any other library function use the dot
form, e.g. std.string.split(s, ",").