On this page:
2.1 Specification
2.2 Grammar
7.1

2 Threading II

In Threading I, you created a ~> macro that threads values through functions. However, it was fairly limited in capability. One major limitation was that it always threaded values into the first argument position. That is, (~> x (f y z)) always produces (f x y z), with x right at the beginning of the argument list.

This works alright a lot of the time, but sometimes, it isn’t enough: not all functions have the “interesting” argument in the first position, and what’s “interesting” might change depending on what you’re doing. Take, for example, the following expression:

(- (apply + (map add1 '(1 2 3))) 1)

This expression is nested, which can be confusing to read, and converting it to a pipeline could make it much clearer. Unfortunately, we cannot use our ~> macro to simplify it, since if we wrote

(~> '(1 2 3)
    (map add1)
    (apply +)
    (- 1))

we would find the arguments end up in the wrong positions. The above expression would actually be equivalent to this:

(- (apply (map '(1 2 3) add1) +) 1)

This is wrong, since it attempts to run (map '(1 2 3) add1), which is nonsensical—we cannot apply a list as a function! The problem with our existing ~> macro is that map and apply expect their “interesting” arguments at the end of the argument list, instead of at the beginning.

It might be tempting to create an alternative macro that’s just like ~> but threads into the final position instead of the first, but that wouldn’t work either, since (- 1) needs the argument threaded into the first position. What we really want is to be able to control the threading position as necessary, overriding the default on a case-by-case basis.

To do this, we will introduce a “hole marker”, _, which can explicitly indicate the location of the threaded expression. It’s probably easiest to illustrate with an example:

(~> '(1 2 3)
    (map add1 _)
    (apply + _)
    (- _ 1))

The ~> macro will recognize the hole marker specially, and it will adjust the threading position based on its location, producing the original, correct expression.

2.1 Specification

Our new ~> macro will work exactly the same as the old one, except that we will introduce an additional rule, which takes priority over the other rules:

Rule 4. For any function f and arguments a ... and b ..., (~> x (f a ... _ b ...) more ...) is equivalent to (~> (f a ... x b ...) more ...).

For example, (~> 1 (list-ref '(10 20) _)) expands to (~> (list-ref '(10 20)) 1) and (~> 2 (substring "hello, world" _ 6)) expands to (~> (substring "hello, world" 2 6)).

Examples:
> (~> 1 (list-ref '(10 20) _))

20

> (~> 2 (substring "hello, world" _ 6))

"llo,"

> (~> '(1 2 3)
      (map add1 _)
      (apply + _)
      (- 1))

8

2.2 Grammar

syntax

(~> val-expr clause ...)

 
clause = (fn-expr pre-expr ... _ post-expr ...)
  | (fn-expr arg-expr ...)
  | bare-id