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:
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:
This is wrong, since it attempts to run (map '(1 2 3) add1), which is nonsensical—
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)).
> (~> 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