On this page:
1.1 How ~> works
1.2 Controlling the threading position with _
1.3 Functions that thread:   lambda~> and lambda~>*
1.4 Threading into non-function forms

1 Introduction: Reducing nesting with ~>🔗

When reading and writing programs, we often conceptualize the behavior of a nested expression as a series of steps. For example, we might understand the expression

(- (sqrt (add1 3)))

by considering how the value 3 “flows through” the operations add1, sqrt, and - in turn to yield the final result of -2. For such a small, simple expression, this interpretation comes entirely automatically, but ease of understanding can rapidly diminish as expressions grow larger. For example, the expression
is only slightly more involved, but the sequence of operations being performed can be much more challenging to immediately visually discern.

The goal of this library is to allow nested expressions like the ones above to be written in an alternate, “pipeline-like” form that more closely matches the sequential way we think about them. The core operator that makes this possible is ~>, which can be used to eliminate the nesting in both of the above expressions:
> (~> 3
      add1
      sqrt
      -)

-2

> (~> 'ABC
      symbol->string
      string->bytes/utf-8
      (bytes-ref 1)
      (- 65))

1

Each use of ~> can be read as a pipeline in which data flows from top to bottom. The first argument to ~> is the value to be threaded through the pipeline, and the remaining arguments are the pipeline’s steps. Since ~> is a macro that “threads” its argument through each step in the pipeline, we call it a threading macro.

1.1 How ~> works🔗

The ~> operator is a syntactic shorthand for nested expressions, no more and no less. Since it is a macro, each use of ~> expands at compile-time to precisely the nested expression it represents. Expansion proceeds in steps, one for each step in the pipeline, and each expansion step adds a single layer of nesting.

To illustrate, consider the following simple example:
(~> "hello"
    (string-ref 1)
    (char->integer))
In this example, the expression "hello" is threaded through two pipeline steps, (string-ref 1) and (char->integer). Steps are threaded into from top to bottom, so to start, "hello" is threaded into (string-ref 1). When an expression is threaded into a function application, it is inserted as the first argument, so the result after a single expansion step is as follows:
(~> (string-ref "hello" 1)
    (char->integer))

Expansion continues by repeating the same process. (string-ref "hello" 1) is threaded into (char->integer) in the same way, inserting it as the first argument:

(~> (char->integer (string-ref "hello" 1)))

At this point, there are no steps left to be threaded into, so ~> just expands to the argument expression itself:
Now ~> has disappeared from the program completely, and expansion is complete.

Note that, in the above example, both steps in the pipeline were wrapped in parentheses. However, when a step does not actually include any extra arguments, like the (char->integer) step above, the parentheses may be omitted, for convenience. Therefore, we could have written the example
(~> "hello"
    (string-ref 1)
    char->integer)
and it would have expanded in exactly the same way.

These rules are now enough to understand the expansion of both of the examples from the previous section. Let’s repeat the step-by-step illustration with the second one. The starting expression is
(~> 'ABC
    symbol->string
    string->bytes/utf-8
    (bytes-ref 1)
    (- 65))
and as mentioned above, a step that is a bare identifier is just shorthand for a function application with no extra arguments. So we can start by adding parentheses to the first to steps:
(~> 'ABC
    (symbol->string)
    (string->bytes/utf-8)
    (bytes-ref 1)
    (- 65))

Now, expansion proceeds in the same way as before. First, 'ABC is threaded into the first step:
(~> (symbol->string 'ABC)
    (string->bytes/utf-8)
    (bytes-ref 1)
    (- 65))
The process is repeated for the second step:
(~> (string->bytes/utf-8 (symbol->string 'ABC))
    (bytes-ref 1)
    (- 65))
The third step has an extra argument, but the threaded expression always comes first, so we get
(~> (bytes-ref (string->bytes/utf-8 (symbol->string 'ABC)) 1)
    (- 65))
and we repeat the process for the last step:

(~> (- (bytes-ref (string->bytes/utf-8 (symbol->string 'ABC)) 1) 65))

Now there are no steps left, so ~> just expands to its argument to obtain the final result:

1.2 Controlling the threading position with _🔗

In the above examples, the expression always conveniently needed to be threaded into the first argument of each step. However, that might not always be the case. For example, functions like map and filter take their list argument last, so a pipeline like
(~> '(-9 4 -16 25)
    (filter positive?)
    (map sqrt))
would expand to

(map (filter '(-9 4 -16 25) positive?) sqrt)

which wouldn’t be correct. In these cases, _ can be used to explicitly mark the position to thread into:
> (~> '(-9 4 -16 25)
      (filter positive? _)
      (map sqrt _))

'(2 5)

Using _ makes ~> much more flexible. However, note that _ can only be used to control which argument of a pipeline step the expression is threaded into, and it cannot be used to thread an expression into a more deeply nested subexpression. For example, the following does not work as intended:
> (~> '(1 -2 3)
      (positive? (apply + _)))

eval:1:0: _: wildcard not allowed as an expression

  in: _

Instead, the nesting should be broken up into separate steps so that _ isn’t nested inside a subexpression:
> (~> '(1 -2 3)
      (apply + _)
      positive?)

#t

1.3 Functions that thread: lambda~> and lambda~>*🔗

The ~> operation is useful to immediately thread an expression through a pipeline, but sometimes it’s more useful to obtain a function that represents the pipeline itself. For example, consider the following expression:
(map (lambda (x) (~> x symbol->string string-length))
     '(hello goodbye))
Here, the explicit binding of x is really just noise. This situation is common enough to warrant a shorthand syntax, lambda~>:
(map (lambda~> symbol->string string-length)
     '(hello goodbye))

Though lambda~> is just an abbreviation for lambda combined with ~>, it can sometimes be useful even in situations where ~> otherwise would not be. For example, it can be used to express a lightweight form of partial application:
> (filter (lambda~> (> 10)) '(5 10 15 20))

'(15 20)

When used in combination with ~> and higher order functions, it’s possible to express “sub-pipelines” that operate over individual elements of a data structure produced by the outer pipeline:
> (~> (range 20)
      (filter (lambda~> (remainder 3)
                        zero?)
              _)
      (apply + _))

63

Functions produced by lambda~> always accept exactly one argument. A variant form, lambda~>* produces a function that accepts any number of (non-keyword) arguments, and the pipeline operates on the entire argument list. Finally, both forms also have shorthand aliases, λ~> and λ~>*.

1.4 Threading into non-function forms🔗

All of the examples so far have exclusively used ~> to thread expressions into function applications. When used in that way, ~> (and especially lambda~>) can be viewed as essentially equivalent to a use of compose. However, because ~> is a syntactic operation, it can also be used to thread into non-function forms, something compose cannot do.

As an illustration, the following example uses ~> to thread an expression into a use of set!:
> (define x 5)
> (~> (* x 2)
      (+ 15)
      (set! x _))
> x

25

However, this example could still be replicated using function composition, as the use of set! could be wrapped in a lambda. More exotic uses of ~> cannot be replicated in that way, such as threading an expression into the body of a binding form:
> (~> some-variable
      (let ([some-variable 'some-value]) _))

'some-value

Whether or not this is actually a good idea is a matter of personal judgment. Examples like the one above are likely to be quite confusing, as they cannot be understood using the usual top-to-bottom reading of ~>. However, the ability to thread into non-function forms can be useful when those forms are written with ~> in mind, and a number of such forms are described in the following section.