Slide 21 of 74

Pipes

A chain reads in the order it runs
func isEven(n: Int): Bool { return n % 2 == 0 }
func label(n: Int): Str { return "#{n}" }

proc main(): void {
	Int[dyn] nums = [1, 2, 3, 4, 5, 6]

	echo nums | filter(isEven) | map(label) | join(", ")

	// The same call, and what the line above compiles to.
	echo join(map(filter(nums, isEven), label), ", ")
}
What it binds to
func double(n: Int): Int { return n * 2 }

proc main(): void {
	Str[dyn] words = ["a", "b", "c"]

	// Tighter than a comparison, so this is len(words) > 2.
	echo words | len > 2

	// And it takes the value beside it: 1 + double(2), not double(3).
	echo 1 + 2 | double
	echo (1 + 2) | double
	echo 2 ** 3 | double    // 16 — ** binds tighter than a pipe
}

x | f(a) is sugar for f(x, a): the value on the left becomes the call's first argument, so a chain of them reads in the order it runs rather than inside out. | is left-associative, and a stage with nothing else in it needs no parentheses — x | f is f(x), as x | f() is.

It binds tighter than + - * / % and every comparison, and looser than the prefix operators and **, which lets a pipeline sit on either side of one: v | len > 2 compares the length, and 2 ** 3 | f is f(8). It takes the value written beside it, so 1 + 2 | f is 1 + f(2); a computed value goes in parentheses.

A proc, a builtin, a library call and a constructor all pipe. What a pipe leaves behind is exactly the call somebody could have written by hand — there is no pipeline object, and nothing is deferred.