Piping
Readable left-to-right composition
Nanyx supports a pipe operator that sends the value on the left into the first argument of a function: x \f(...).
Why piping?
In any programming language, if the user wishes to call a series of functions on a value (passing the result of one function to the next), they can nest the function calls. The problem with nested function calls is that the functions execute from the inside out and must therefore be read backwards (from right to left), which can be hard to follow:
def getStreamFromInput: Request -> Stream(Byte) = ...
def getBytesFromStream: Stream(Byte) -> string = ...
def deserialisePerson: string -> Person = ...
def personObj = deserialisePerson(getBytesFromStream(getStreamFromInput(request)))
Piping makes sequences of function calls easy to read by allowing the functions to be written in the order they are executed—and is the preferred style for expressions containing nested function calls:
def personObj = request
\getStreamFromInput
\getBytesFromStream
\deserialisePerson
Piping with extra arguments
The piped value will be combined with any additional written arguments into a record to form the argument of the function. For example, x \f(y) is equivalent to f(x, y) and x \f(y, z) is equivalent to f(x, y, z) etc. This allows you to pipe into functions that take more than one argument and, unlike curried functions, the argument order remains the same as the non-piped version.
def add = { x, y -> x + y }
def result = 1
\add(2)
\add(3)
Piping with trailing lambdas
If the last argument of a function is a lambda, you can write it after the piped value without parentheses:
def result = [1, 2, 3]
\map { + 1 }
The piping and trailing lambda syntax can be combined with extra arguments as well:
def result = [1, 2, 3] \fold(0) { + }
This is a relatively simply syntax transformation, which becomes
def result = fold([1, 2, 3], 0, { + })
Qualified piping
If the function is attached to the type on the left-hand side of the \, the function does not need to be qualified with the type name. x \f resolves f by the type of x in exactly the same way that x.f does (see Dot access); the two differ only in that \ is preferred for chains and whenever extra arguments are passed.
-- In regular function syntax we must qualify the function with the type name
def parts = String.split("hello world", on = " ")
-- With piping, we can omit the type name because we know the type of the first argument before we need to identify the function
def parts = "hello world" \split(on = " ")
Note that the function can still be qualified in a pipe expression if desired; it might also be required if the type of the first argument has not yet been determined by the time the function is identified:
def f = { x ->
x\foo -- Error: cannot resolve function 'foo' because the type of 'x' is not yet known
x\SomeType.foo -- OK: the function is qualified, so it can be resolved
}