2 Pipeline Helpers
This section documents a handful of helper forms intended to be used as pieces of a larger ~> pipeline.
2.1 Short-circuiting pipelines: and~>
By convention, many Racket operations return #f upon failure, and many others can be instructed to do so. The and~> operator is a short-circuiting variant of ~> that takes advantage of that convention. If any step in the pipeline evaluates to #f, the remaining steps are skipped, and the entire pipeline evaluates to #f:
Although and~> is useful standalone, it is particularly useful when combined with ~>. and~> can be used to mark individual parts of a pipeline short-circuiting, while the rest of the pipeline behaves as normal:
> (define (f str) (~> str string->number (and~> (/ 100)) (or 0) (~r #:precision 2))) > (f "hello") "0"
> (f "42") "0.42"
2.2 Conditional pipelines: when~>, unless~>, and cond~>
Sometimes, it can be useful to conditionally skip a portion of a pipeline. For example, consider the following function, which formats numbers as either decimals or percentages:
> (define (format-number n #:percent? percent?) (~> n (when~> percent? (* 100)) (~r #:precision 2) (when~> percent? (string-append "%")))) > (format-number (sin 1) #:percent? #f) "0.84"
> (format-number (sin 1) #:percent? #t) "84.15%"
The use of when~> allows the (* 100) and (string-append "%") pipeline steps to be skipped if the percent? condition is #f.
Note that, since when~> is intended to be used inside a ~> pipeline, it actually accepts an argument to be threaded before the condition expression. This can appear quite strange if used standalone:
> (when~> 'hello #t symbol->string string-upcase) "HELLO"
The counterpart to when~> is unless~>, which works the same way but with its condition inverted. For even more flexibility, cond~> can be used to switch between several pipeline alternatives.
2.3 Pipeline splitting: tee~>
Suppose we have a complex pipeline and would like to inspect how it is behaving by printing the intermediate value at each pipeline step. We could insert uses of println into the pipeline, but since println returns #<void>, that would prevent the value from flowing to later pipeline steps. The solution is to use the tee~> operator:
> (~> (range 8) (tee~> println) (map sqr _) (tee~> println) (filter-not (λ~> (remainder 4) zero?) _) (tee~> println) (apply + _))
'(0 1 2 3 4 5 6 7)
'(0 1 4 9 16 25 36 49)
'(1 9 25 49)
84
tee~> is so named because it is like a pipe T-splitter: it sends its input into a separate pipeline, which is executed only for side-effects, then returns its input value so the original pipeline may continue.