Operators for function application

When working with Unison code, you might encounter the following operators which are provided by the base library. They are commonly used for manipulating the order in which a function is applied to its arguments.

a << b

<< : (b ->{𝕖} c) -> (a ->{𝕖} b) -> a ->{𝕖} c

<<, also known as "compose," takes in two functions b -> c and a -> b and returns a function from a -> c.

composeEx : Nat -> Boolean
composeEx = Boolean.not << Nat.isEven

Piping multiple functions together with the compose operator looks like:

composeMultiEx : Text -> Boolean
composeMultiEx = Boolean.not << Nat.isEven << Text.size

The expression is actually parsed:

((Boolean.not << Nat.isEven) << Text.size)

Giving us a function that expects Text.size's argument of Text and returning the final result of applying the Boolean.not function.

πŸ“Œ
Operators and infix function application are grouped with the leftmost expressions happening first. In other words they are left-associative. Read more about this in the language guide section about operators and function application

a >> b

>> : (a ->{𝕖} b) -> (b ->{𝕖} c) -> a ->{𝕖} c

>>, also known as andThen, is like compose with the function argument order reversed. It takes in two functions a->b and b->c and returns a function from a->c.

andThenEx : Boolean
andThenEx = ((>>) Nat.isEven Boolean.not 4)

Piping together multiple >> calls looks like:

andThenMultiEx : Boolean
andThenMultiEx = ((>>) (Text.size >> Nat.isEven) Boolean.not "Boo")

a |> b

|> : a -> (a ->{𝕖} b) ->{𝕖} b

While >> returns a function, |> allows us to apply an argument to a function, returning the value of that function call. When using |> it's helpful to remember that the function argument should be on the left hand side of the operator. The value of this operator might not be immediately obvious--after all, why use special operators for function application when parentheses will suffice πŸ€”--but you'll often see multiple instances of the |> operator chained together to form a complete expression.

pipeRightEx : Optional Nat
pipeRightEx =
  use Nat +
  Some 5 |> Optional.filter Nat.isEven |> Optional.map (n -> n + 1)

You might find that there's a greater fit between the semantic order of this expression and the order in which the function calls are applied than in the non |> version of this expression:

🧠

When chaining the |> operator in a multi-line expression, if the operator is the first thing on a new line, it should be indented.

List.range 0 10
   |> map (pow 2)
   |> sum

If the operator is the last thing on the line, the following expression does not need to be indented.

range 0 10 |>
map (pow 2) |>
sum

a <| b

<| : (a ->{𝕖} b) -> a ->{𝕖} b

When using <|, the function should be on the left hand side of the operator with the function's argument on the right hand side. You can read the <| operator as "pipe backwards." You might choose to use this operator when the argument to the function in question needs some "pre-processing" which would otherwise involve parentheses.

Note that the <| operator does not change the precedence of the operator function calls. The leftmost sub-expression is still executed first. You can use parentheses to change the grouping.