├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── threading-doc ├── info.rkt └── scribblings │ ├── info.rkt │ └── threading.scrbl ├── threading-lib ├── info.rkt └── threading │ ├── main.rkt │ └── private │ ├── base.rkt │ ├── cond.rkt │ └── extra.rkt ├── threading-test ├── info.rkt └── tests │ └── threading │ ├── cond.rkt │ ├── extra.rkt │ └── main.rkt └── threading └── info.rkt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push] 3 | defaults: 4 | run: 5 | working-directory: repo 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | racket-version: [ '6.7', '6.12', '7.0', '7.9', '8.0', stable ] 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: { path: repo } 16 | - uses: Bogdanp/setup-racket@v1.5 17 | with: 18 | version: ${{ matrix.racket-version }} 19 | dest: '$GITHUB_WORKSPACE/racket' 20 | sudo: never 21 | - name: install 22 | run: raco pkg install --installation --auto --link threading-{doc,lib,test} 23 | - name: test 24 | run: raco test -ep threading-{doc,lib,test} 25 | 26 | - name: deploy_docs 27 | if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/master' && matrix.racket-version == 'stable' }} 28 | run: | 29 | set -e 30 | scribble +m --redirect https://docs.racket-lang.org/local-redirect/index.html \ 31 | --htmls --dest-name docs threading-doc/scribblings/threading.scrbl 32 | cd docs 33 | git init -b gh-pages 34 | git config user.name 'GitHub Actions' 35 | git config user.email 'lexi.lambda@gmail.com' 36 | git add . 37 | git commit -m 'Deploy to GitHub Pages' 38 | git push --force 'https://lexi-lambda:${{ github.token }}@github.com/${{ github.repository }}' gh-pages 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Racket compiled files 2 | compiled/ 3 | 4 | # Scribble documentation files 5 | doc/ 6 | docs/ 7 | 8 | # cover-generated code coverage reports 9 | coverage/ 10 | 11 | # common backups, autosaves, lock files, OS meta-files 12 | *~ 13 | \#* 14 | .#* 15 | .DS_Store 16 | *.bak 17 | TAGS 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Alexis King 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # threading [![Build Status](https://img.shields.io/github/actions/workflow/status/lexi-lambda/threading/build.yml?branch=master)](https://github.com/lexi-lambda/threading/actions/workflows/build.yml) [![Scribble Docs](https://img.shields.io/badge/docs-built-blue)][threading-doc] 2 | 3 | This package implements threading macros for Racket. 4 | 5 | # Installation 6 | 7 | ```sh 8 | $ raco pkg install threading 9 | ``` 10 | 11 | # Usage 12 | 13 | ```racket 14 | > (~> 'abc 15 | symbol->string 16 | string->bytes/utf-8 17 | (bytes-ref 1) 18 | (- 2)) 19 | 96 20 | ``` 21 | 22 | For more information, see [the documentation][threading-doc]. 23 | 24 | [threading-doc]: http://lexi-lambda.github.io/threading/ 25 | -------------------------------------------------------------------------------- /threading-doc/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define version "2.0") 4 | 5 | (define license 'ISC) 6 | 7 | (define collection 'multi) 8 | 9 | (define deps 10 | '()) 11 | (define build-deps 12 | '("base" 13 | "racket-doc" 14 | "scribble-lib" 15 | ["threading-lib" #:version "2.0"])) 16 | -------------------------------------------------------------------------------- /threading-doc/scribblings/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define scribblings '(["threading.scrbl" (multi-page)])) 4 | -------------------------------------------------------------------------------- /threading-doc/scribblings/threading.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @(begin 4 | (require (for-label racket/base 5 | racket/format 6 | racket/function 7 | racket/list 8 | racket/match 9 | racket/math 10 | racket/string 11 | threading) 12 | scribble/example) 13 | 14 | (define (reftech . pre-content) 15 | (apply tech pre-content #:doc '(lib "scribblings/reference/reference.scrbl"))) 16 | 17 | (define make-threading-eval (make-eval-factory '(racket/format 18 | racket/list 19 | racket/match 20 | racket/math 21 | racket/string 22 | threading))) 23 | 24 | (define-syntax-rule (threading-examples body ...) 25 | (examples #:eval (make-threading-eval) #:once body ...)) 26 | (define-syntax-rule (threading-interaction body ...) 27 | (threading-examples #:label #f body ...)) 28 | 29 | (define ooo (racketmetafont "..."))) 30 | 31 | @title{Threading Macros} 32 | @author{@author+email["Alexis King" "lexi.lambda@gmail.com"]} 33 | 34 | @defmodule[threading] 35 | 36 | @margin-note{Arguably, “pipeline macros” would be a much better name than “threading macros”, as the forms provided by this package have nothing to do with @reftech{threads}. The choice of the word “threading” comes from @hyperlink["https://clojure.org/guides/threading_macros"]{Clojure’s threading macros}, which were the original inspiration for this package. The author apologizes for not having the prudence to borrow the idea under a different name.} 37 | 38 | This library provides a set of @italic{threading macros} that help flatten nested expressions into a more readable form. In essence, they allow a sequence of transformations to be expressed as a “pipeline”, similar to Unix pipes. Alongside the core threading operator, @racket[~>], @racketmodname[threading] provides a number of variants and helper macros useful for pipeline-oriented programming. 39 | 40 | @local-table-of-contents[] 41 | 42 | @; ----------------------------------------------------------------------------- 43 | 44 | @section[#:tag "introduction"]{Introduction: Reducing nesting with @racket[~>]} 45 | 46 | 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 47 | @; 48 | @(racketblock 49 | (- (sqrt (add1 3)))) 50 | @; 51 | by considering how the value @racket[3] “flows through” the operations @racket[add1], @racket[sqrt], and @racket[-] in turn to yield the final result of @racket[-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 52 | @; 53 | @(racketblock 54 | (- (bytes-ref (string->bytes/utf-8 (symbol->string 'ABC)) 1) 65)) 55 | @; 56 | is only slightly more involved, but the sequence of operations being performed can be much more challenging to immediately visually discern. 57 | 58 | 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 @racket[~>], which can be used to eliminate the nesting in both of the above expressions: 59 | @; 60 | @(threading-interaction 61 | (eval:check (~> 3 62 | add1 63 | sqrt 64 | -) 65 | -2) 66 | (eval:check (~> 'ABC 67 | symbol->string 68 | string->bytes/utf-8 69 | (bytes-ref 1) 70 | (- 65)) 71 | 1)) 72 | 73 | Each use of @racket[~>] can be read as a pipeline in which data flows from top to bottom. The first argument to @racket[~>] is the value to be threaded through the pipeline, and the remaining arguments are the pipeline’s steps. Since @racket[~>] is a macro that “threads” its argument through each step in the pipeline, we call it a @italic{threading macro}. 74 | 75 | @subsection[#:tag "how-it-works"]{How @racket[~>] works} 76 | 77 | The @racket[~>] operator is a syntactic shorthand for nested expressions, no more and no less. Since it is a macro, each use of @racket[~>] 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. 78 | 79 | To illustrate, consider the following simple example: 80 | @; 81 | @(racketblock 82 | (~> "hello" 83 | (string-ref 1) 84 | (char->integer))) 85 | @; 86 | In this example, the expression @racket["hello"] is threaded through two pipeline steps, @racket[(string-ref 1)] and @racket[(char->integer)]. Steps are threaded into from top to bottom, so to start, @racket["hello"] is threaded into @racket[(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: 87 | @; 88 | @(racketblock 89 | (~> (string-ref "hello" 1) 90 | (char->integer))) 91 | 92 | Expansion continues by repeating the same process. @racket[(string-ref "hello" 1)] is threaded into @racket[(char->integer)] in the same way, inserting it as the first argument: 93 | @; 94 | @(racketblock 95 | (~> (char->integer (string-ref "hello" 1)))) 96 | @; 97 | At this point, there are no steps left to be threaded into, so @racket[~>] just expands to the argument expression itself: 98 | @; 99 | @(racketblock 100 | (char->integer (string-ref "hello"))) 101 | @; 102 | Now @racket[~>] has disappeared from the program completely, and expansion is complete. 103 | 104 | 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 @racket[(char->integer)] step above, the parentheses may be omitted, for convenience. Therefore, we could have written the example 105 | @; 106 | @(racketblock 107 | (~> "hello" 108 | (string-ref 1) 109 | char->integer)) 110 | @; 111 | and it would have expanded in exactly the same way. 112 | 113 | 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 114 | @; 115 | @(racketblock 116 | (~> 'ABC 117 | symbol->string 118 | string->bytes/utf-8 119 | (bytes-ref 1) 120 | (- 65))) 121 | @; 122 | 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: 123 | @; 124 | @(racketblock 125 | (~> 'ABC 126 | (symbol->string) 127 | (string->bytes/utf-8) 128 | (bytes-ref 1) 129 | (- 65))) 130 | 131 | Now, expansion proceeds in the same way as before. First, @racket['ABC] is threaded into the first step: 132 | @; 133 | @(racketblock 134 | (~> (symbol->string 'ABC) 135 | (string->bytes/utf-8) 136 | (bytes-ref 1) 137 | (- 65))) 138 | @; 139 | The process is repeated for the second step: 140 | @; 141 | @(racketblock 142 | (~> (string->bytes/utf-8 (symbol->string 'ABC)) 143 | (bytes-ref 1) 144 | (- 65))) 145 | @; 146 | The third step has an extra argument, but the threaded expression always comes first, so we get 147 | @; 148 | @; 149 | @(racketblock 150 | (~> (bytes-ref (string->bytes/utf-8 (symbol->string 'ABC)) 1) 151 | (- 65))) 152 | @; 153 | and we repeat the process for the last step: 154 | @; 155 | @(racketblock 156 | (~> (- (bytes-ref (string->bytes/utf-8 (symbol->string 'ABC)) 1) 65))) 157 | 158 | Now there are no steps left, so @racket[~>] just expands to its argument to obtain the final result: 159 | @; 160 | @(racketblock 161 | (- (bytes-ref (string->bytes/utf-8 (symbol->string 'ABC)) 1) 65)) 162 | 163 | @subsection[#:tag "threading-position"]{Controlling the threading position with @racket[_]} 164 | 165 | 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 @racket[map] and @racket[filter] take their list argument @emph{last}, so a pipeline like 166 | @; 167 | @(racketblock 168 | (~> '(-9 4 -16 25) 169 | (filter positive?) 170 | (map sqrt))) 171 | @; 172 | would expand to @(racketblock (map (filter '(-9 4 -16 25) positive?) sqrt)) which wouldn’t be correct. In these cases, @racket[_] can be used to explicitly mark the position to thread into: 173 | @; 174 | @(threading-interaction 175 | (~> '(-9 4 -16 25) 176 | (filter positive? _) 177 | (map sqrt _))) 178 | 179 | Using @racket[_] makes @racket[~>] much more flexible. However, note that @racket[_] 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: 180 | @; 181 | @(threading-interaction 182 | (eval:error (~> '(1 -2 3) 183 | (positive? (apply + _))))) 184 | @; 185 | Instead, the nesting should be broken up into separate steps so that @racket[_] isn’t nested inside a subexpression: 186 | @; 187 | @(threading-interaction 188 | (eval:check (~> '(1 -2 3) 189 | (apply + _) 190 | positive?) 191 | #t)) 192 | 193 | @subsection[#:tag "functions-that-thread"]{Functions that thread: @racket[lambda~>] and @racket[lambda~>*]} 194 | 195 | The @racket[~>] 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: 196 | @; 197 | @(racketblock 198 | (map (lambda (x) (~> x symbol->string string-length)) 199 | '(hello goodbye))) 200 | @; 201 | Here, the explicit binding of @racket[x] is really just noise. This situation is common enough to warrant a shorthand syntax, @racket[lambda~>]: 202 | @; 203 | @(racketblock 204 | (map (lambda~> symbol->string string-length) 205 | '(hello goodbye))) 206 | 207 | Though @racket[lambda~>] is just an abbreviation for @racket[lambda] combined with @racket[~>], it can sometimes be useful even in situations where @racket[~>] otherwise would not be. For example, it can be used to express a lightweight form of partial application: 208 | @; 209 | @(threading-interaction 210 | (eval:check (filter (lambda~> (> 10)) '(5 10 15 20)) '(15 20))) 211 | 212 | When used in combination with @racket[~>] 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: 213 | @; 214 | @(threading-interaction 215 | (eval:check (~> (range 20) 216 | (filter (lambda~> (remainder 3) 217 | zero?) 218 | _) 219 | (apply + _)) 220 | 63)) 221 | 222 | Functions produced by @racket[lambda~>] always accept exactly one argument. A variant form, @racket[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, @racket[λ~>] and @racket[λ~>*]. 223 | 224 | @subsection[#:tag "threading-into-forms"]{Threading into non-function forms} 225 | 226 | All of the examples so far have exclusively used @racket[~>] to thread expressions into function applications. When used in that way, @racket[~>] (and especially @racket[lambda~>]) can be viewed as essentially equivalent to a use of @racket[compose]. However, because @racket[~>] is a syntactic operation, it can also be used to thread into non-function forms, something @racket[compose] cannot do. 227 | 228 | As an illustration, the following example uses @racket[~>] to thread an expression into a use of @racket[set!]: 229 | @; 230 | @(threading-interaction 231 | (define x 5) 232 | (~> (* x 2) 233 | (+ 15) 234 | (set! x _)) 235 | (eval:check x 25)) 236 | @; 237 | However, this example could still be replicated using function composition, as the use of @racket[set!] could be wrapped in a @racket[lambda]. More exotic uses of @racket[~>] cannot be replicated in that way, such as threading an expression into the body of a binding form: 238 | @; 239 | @(threading-interaction 240 | (eval:check (~> some-variable 241 | (let ([some-variable 'some-value]) _)) 242 | 'some-value)) 243 | 244 | 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 @racket[~>]. However, the ability to thread into non-function forms can be useful when those forms are written with @racket[~>] in mind, and a number of such forms are described in the following section. 245 | 246 | @; ----------------------------------------------------------------------------- 247 | 248 | @section[#:tag "pipeline-helpers"]{Pipeline Helpers} 249 | 250 | This section documents a handful of helper forms intended to be used as pieces of a larger @racket[~>] pipeline. 251 | 252 | @subsection[#:tag "short-circuiting-pipelines"]{Short-circuiting pipelines: @racket[and~>]} 253 | 254 | By convention, many Racket operations return @racket[#f] upon failure, and many others can be instructed to do so. The @racket[and~>] operator is a short-circuiting variant of @racket[~>] that takes advantage of that convention. If any step in the pipeline evaluates to @racket[#f], the remaining steps are skipped, and the entire pipeline evaluates to @racket[#f]: 255 | @; 256 | @(threading-interaction 257 | (eval:check (and~> '(1 2 3) 258 | (findf even? _) 259 | (* 2)) 260 | 4) 261 | (eval:check (and~> '(1 3 5) 262 | (findf even? _) 263 | (* 2)) 264 | #f)) 265 | 266 | Although @racket[and~>] is useful standalone, it is particularly useful when combined with @racket[~>]. @racket[and~>] can be used to mark individual @emph{parts} of a pipeline short-circuiting, while the rest of the pipeline behaves as normal: 267 | @; 268 | @(threading-interaction 269 | (define (f str) 270 | (~> str 271 | string->number 272 | (and~> (/ 100)) 273 | (or 0) 274 | (~r #:precision 2))) 275 | (eval:check (f "hello") "0") 276 | (eval:check (f "42") "0.42")) 277 | 278 | @subsection[#:tag "conditional-pipelines"]{Conditional pipelines: @racket[when~>], @racket[unless~>], and @racket[cond~>]} 279 | 280 | 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: 281 | @; 282 | @(threading-interaction 283 | (define (format-number n #:percent? percent?) 284 | (~> n 285 | (when~> percent? 286 | (* 100)) 287 | (~r #:precision 2) 288 | (when~> percent? 289 | (string-append "%")))) 290 | (eval:check (format-number (sin 1) #:percent? #f) "0.84") 291 | (eval:check (format-number (sin 1) #:percent? #t) "84.15%")) 292 | 293 | The use of @racket[when~>] allows the @racket[(* 100)] and @racket[(string-append "%")] pipeline steps to be skipped if the @racket[percent?] condition is @racket[#f]. 294 | 295 | Note that, since @racket[when~>] is intended to be used inside a @racket[~>] pipeline, it actually accepts an argument to be threaded @emph{before} the condition expression. This can appear quite strange if used standalone: 296 | @; 297 | @(threading-interaction 298 | (eval:check (when~> 'hello 299 | #t 300 | symbol->string 301 | string-upcase) 302 | "HELLO")) 303 | 304 | The counterpart to @racket[when~>] is @racket[unless~>], which works the same way but with its condition inverted. For even more flexibility, @racket[cond~>] can be used to switch between several pipeline alternatives. 305 | 306 | @subsection[#:tag "pipeline-splitting"]{Pipeline splitting: @racket[tee~>]} 307 | 308 | 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 @racket[println] into the pipeline, but since @racket[println] returns @void-const, that would prevent the value from flowing to later pipeline steps. The solution is to use the @racket[tee~>] operator: 309 | @; 310 | @(threading-interaction 311 | (eval:check (~> (range 8) 312 | (tee~> println) 313 | (map sqr _) 314 | (tee~> println) 315 | (filter-not (λ~> (remainder 4) zero?) _) 316 | (tee~> println) 317 | (apply + _)) 318 | 84)) 319 | 320 | @racket[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. 321 | 322 | @; ----------------------------------------------------------------------------- 323 | 324 | @section[#:tag "api-reference"]{API Reference} 325 | 326 | @(define datum-clause @racket[(quote @#,racket[_datum])]) 327 | 328 | @defform[#:literals (_ quote) 329 | (~> form clause ...) 330 | #:grammar 331 | ([clause bare-id 332 | #,datum-clause 333 | (head arg ...) 334 | (pre ... _ post ...)])]{ 335 | “Threads” @racket[form] through each @racket[clause], from top to bottom, as 336 | described in @secref["how-it-works"]. More precisely, @racket[~>] expands 337 | according to the following rules: 338 | 339 | @itemlist[ 340 | #:style 'compact 341 | @item{@racket[(~> form)] expands to simply @racket[form].} 342 | @item{@racket[(~> form bare-id)] expands to @racket[(bare-id form)].} 343 | @item{@racket[(~> form #,datum-clause)] expands to @racket[(#,datum-clause form)].} 344 | @item{@racket[(~> form (head arg #,ooo))] expands to @racket[(head form arg #,ooo)]} 345 | @item{@racket[(~> form (pre #,ooo _ post #,ooo))] expands to @racket[(pre #,ooo form post #,ooo)].} 346 | @item{@racket[(~> form _clause1 _clause2 #,ooo)] expands to @racket[(~> (~> form _clause1) _clause2 #,ooo)].}] 347 | 348 | The special treatment of @datum-clause clauses is unlikely to be useful to programs written in @hash-lang[]@|~|@racketmodname[racket], but it may be useful in languages that provide an alternate binding for @racket[#%app]. 349 | 350 | @(threading-examples 351 | (~> '(1 2 3) 352 | (map add1 _) 353 | second 354 | (* 2)) 355 | (~> "foo" 356 | string->bytes/utf-8 357 | bytes->list 358 | (map (λ~> (* 2)) _) 359 | list->bytes)) 360 | 361 | @history[#:changed "1.3" @elem{Removed the restriction that at least one 362 | @racket[pre] form must be present in a @racket[clause] containing 363 | @racket[_].}]} 364 | 365 | @deftogether[(@defform[(lambda~> clause ...)] 366 | @defform[(λ~> clause ...)])]{ 367 | Equivalent to @racket[(λ (x) (~> x clause #,ooo))]. 368 | 369 | @(threading-examples 370 | (map (λ~> add1 (* 2)) (range 5)))} 371 | 372 | @deftogether[(@defform[(lambda~>* clause ...)] 373 | @defform[(λ~>* clause ...)])]{ 374 | Equivalent to @racket[(λ args (~> args clause #,ooo))]. 375 | 376 | @(threading-examples 377 | ((λ~>* second sqr) 1 2 3))} 378 | 379 | @defform[(and~> expr clause ...)]{ 380 | Works like @racket[~>], but if any of the intermediate expressions returns @racket[#f], threading 381 | stops, and the result of the whole expression is @racket[#f]. Like @racket[and], @racket[and~>] is 382 | short-circuiting, so the remaining steps will not be evaluated. 383 | 384 | @(threading-examples 385 | (and~> '(1 3 5) 386 | (map add1 _) 387 | (findf even? _)) 388 | (and~> '(2 4 6) 389 | (map add1 _) 390 | (findf even? _)))} 391 | 392 | @defform[(tee~> expr clause ...)]{ 393 | @margin-note{See @secref["pipeline-splitting"] for an example of using @racket[tee~>].} 394 | 395 | Evaluates @racket[expr], then threads the result through each @racket[clause] as in @racket[~>]. The result of @racket[expr] is the result of the @racket[tee~>] expression. 396 | 397 | @racket[tee~>] is equivalent to the following: 398 | @; 399 | @(racketblock 400 | (let ([tmp expr]) 401 | (~> tmp clause #,ooo) 402 | tmp)) 403 | 404 | @history[#:added "2.0"]} 405 | 406 | @subsection[#:tag "api-reference:conditional"]{Conditional Operators} 407 | 408 | @defform[(when~> val-expr test-expr clause ...)]{ 409 | Evaluates @racket[val-expr] and @racket[test-expr]. If the result of @racket[test-expr] is @racket[#f], the result of the @racket[when~>] expression is the result of @racket[val-expr]. Otherwise, the result of @racket[val-expr] is threaded through each @racket[clause] as in @racket[~>]. 410 | 411 | @racket[when~>] is equivalent to the following: 412 | @; 413 | @(racketblock 414 | (let ([tmp val-expr]) 415 | (if test-expr 416 | (~> tmp clause #,ooo) 417 | tmp))) 418 | 419 | The @racket[when~>] form is intended to be used as part of a larger @racket[~>] pipeline; see @secref["conditional-pipelines"] for an example. 420 | 421 | @history[#:added "2.0"]} 422 | 423 | @defform[(unless~> val-expr test-expr clause ...)]{ 424 | Equivalent to @racket[(when~> val-expr (not test-expr) clause #,ooo)]. 425 | 426 | @history[#:added "2.0"]} 427 | 428 | @defform[#:literals [else] 429 | (cond~> val-expr cond-clause ... maybe-else-clause) 430 | #:grammar 431 | ([cond-clause [test-expr clause ...]] 432 | [maybe-else-clause (code:line) 433 | [else clause ...]])]{ 434 | Evaluates @racket[val-expr], then evaluates each @racket[test-expr], from top to bottom, until one of them produces a value other than @racket[#f]. The result of @racket[val-expr] is threaded through each @racket[clause] associated with the @racket[test-expr] that produced a non-@racket[#f] value. 435 | 436 | If all @racket[test-expr] expressions produce @racket[#f] and an @racket[else] clause was provided, the result of @racket[val-expr] is threaded through each @racket[clause] contained in the @racket[else] clause. Otherwise, the result is the result of @racket[val-expr]. 437 | 438 | @racket[cond~>] is equivalent to the following: 439 | @; 440 | @(racketblock 441 | (let ([tmp val-expr]) 442 | (cond 443 | [test-expr (~> tmp clause #,ooo)] #,ooo 444 | [else (~> tmp clause #,ooo)]))) 445 | 446 | Like @racket[when~>], @racket[cond~>] is intended to be used as a part of a larger @racket[~>] pipeline. 447 | 448 | @(threading-examples 449 | (define (f n) 450 | (~> 11 451 | (cond~> 452 | [(= n 1) add1 (* 2)] 453 | [(= n 2) sub1 (/ 2)] 454 | [else -]))) 455 | 456 | (eval:check (f 1) 24) 457 | (eval:check (f 2) 5) 458 | (eval:check (f 3) -11)) 459 | 460 | @history[#:added "2.0"]} 461 | 462 | @subsection[#:tag "api-reference:legacy"]{Deprecated Forms} 463 | 464 | @defform[(~>> form clause ...)]{ 465 | @deprecated[#:what "form" @racket[~>]] 466 | 467 | Equivalent to @racket[~>], except that @racket[form] is inserted at the @emph{end} of a clause of the form @racket[(head arg #,ooo)], rather than between the @racket[head] and @racket[arg] forms. 468 | 469 | @history[#:changed "2.0" @elem{Deprecated in favor of using @racket[~>] with @racket[_].}]} 470 | 471 | @defform[(and~>> expr clause ...)]{ 472 | @deprecated[#:what "form" @racket[and~>]] 473 | 474 | Combines the threading behavior of @racket[~>>] and the short-circuiting behavior of @racket[and~>]. 475 | 476 | @history[#:changed "2.0" @elem{Deprecated in favor of using @racket[and~>] with @racket[_].}]} 477 | 478 | @deftogether[(@defform[(lambda~>> clause ...)] 479 | @defform[(λ~>> clause ...)] 480 | @defform[(lambda~>>* clause ...)] 481 | @defform[(λ~>>* clause ...)] 482 | @defform[(lambda-and~> clause ...)] 483 | @defform[(λ-and~> clause ...)] 484 | @defform[(lambda-and~>> clause ...)] 485 | @defform[(λ-and~>> clause ...)] 486 | @defform[(lambda-and~>* clause ...)] 487 | @defform[(λ-and~>* clause ...)] 488 | @defform[(lambda-and~>>* clause ...)] 489 | @defform[(λ-and~>>* clause ...)])]{ 490 | @deprecated[#:what "form" @elem{@racket[lambda~>], @racket[lambda~>*], and @racket[and~>]}] 491 | 492 | Like @racket[lambda~>], but uses @racket[~>>] instead of @racket[~>]. 493 | 494 | @history[#:changed "2.0" @elem{Deprecated in favor of combining other forms.}]} 495 | 496 | -------------------------------------------------------------------------------- /threading-lib/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define version "2.0") 4 | 5 | (define license 'ISC) 6 | 7 | (define collection 'multi) 8 | 9 | (define deps 10 | '(["base" #:version "6.3"])) 11 | (define build-deps 12 | '()) 13 | -------------------------------------------------------------------------------- /threading-lib/threading/main.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require "private/base.rkt" 4 | "private/cond.rkt" 5 | "private/extra.rkt") 6 | 7 | (provide (all-from-out "private/base.rkt" 8 | "private/cond.rkt" 9 | "private/extra.rkt")) 10 | -------------------------------------------------------------------------------- /threading-lib/threading/private/base.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require (for-syntax racket/base 4 | racket/list 5 | syntax/parse/pre)) 6 | 7 | (provide ~> ~>> and~> and~>> _ 8 | lambda~> lambda~>> lambda~>* lambda~>>* 9 | lambda-and~> lambda-and~>> lambda-and~>* lambda-and~>>* 10 | (rename-out [lambda~> λ~>] [lambda~>> λ~>>] 11 | [lambda~>* λ~>*] [lambda~>>* λ~>>*] 12 | [lambda-and~> λ-and~>] [lambda-and~>> λ-and~>>] 13 | [lambda-and~>* λ-and~>*] [lambda-and~>>* λ-and~>>*])) 14 | 15 | (begin-for-syntax 16 | (define (build-insert #:pre pre-stxs 17 | #:post post-stxs 18 | #:context ctxt 19 | #:props props) 20 | (λ (e-stx) 21 | (datum->syntax ctxt 22 | (append pre-stxs (cons e-stx post-stxs)) 23 | ctxt 24 | props))) 25 | 26 | (define-syntax-class (clause mode) ; mode : (or/c '~> '~>>) 27 | #:literals [_ quote] 28 | #:attributes [insert] ; insert : (-> syntax? syntax?) 29 | (pattern {~or {~var _ id} 30 | (quote term)} 31 | #:attr insert (build-insert #:pre (list this-syntax) 32 | #:post '() 33 | #:context this-syntax 34 | #:props #f)) 35 | (pattern (pre ... _ post ...) 36 | #:attr insert (build-insert #:pre (attribute pre) 37 | #:post (attribute post) 38 | #:context this-syntax 39 | #:props this-syntax)) 40 | (pattern (term ...+) 41 | #:do [(define-values [pre post] 42 | (if (eq? mode '~>) 43 | (split-at (attribute term) 1) 44 | (values (attribute term) '())))] 45 | #:attr insert (build-insert #:pre pre 46 | #:post post 47 | #:context this-syntax 48 | #:props this-syntax)))) 49 | 50 | (define-syntaxes [~> ~>>] 51 | (let () 52 | (define (make mode) 53 | (syntax-parser 54 | [(_ e:expr) #'e] 55 | [(head e:expr {~var c (clause mode)} rest ...) 56 | (datum->syntax 57 | this-syntax 58 | (list* #'head ((attribute c.insert) #'e) (attribute rest)) 59 | this-syntax 60 | this-syntax)])) 61 | 62 | (values (make '~>) (make '~>>)))) 63 | 64 | (define-syntaxes [and~> and~>>] 65 | (let () 66 | (define (make mode) 67 | (syntax-parser 68 | [(_ e:expr) #'e] 69 | [(head e:expr {~var c (clause mode)} rest ...) 70 | (quasisyntax/loc this-syntax 71 | (let ([tmp e]) 72 | (and tmp 73 | #,(datum->syntax 74 | this-syntax 75 | (list* #'head 76 | ((attribute c.insert) #'tmp) 77 | (attribute rest)) 78 | this-syntax 79 | this-syntax))))])) 80 | 81 | (values (make '~>) (make '~>>)))) 82 | 83 | (define-syntaxes [lambda~> lambda~>> lambda-and~> lambda-and~>>] 84 | (let () 85 | (define (make ~>-id) 86 | (syntax-parser 87 | [(_ . body) 88 | (quasisyntax/loc this-syntax 89 | (lambda (x) 90 | #,(quasisyntax/loc this-syntax 91 | (#,~>-id x . body))))])) 92 | 93 | (values (make #'~>) (make #'~>>) (make #'and~>) (make #'and~>>)))) 94 | 95 | (define-syntaxes [lambda~>* lambda~>>* lambda-and~>* lambda-and~>>*] 96 | (let () 97 | (define (make ~>-id) 98 | (syntax-parser 99 | [(_ . body) 100 | (quasisyntax/loc this-syntax 101 | (lambda args 102 | #,(quasisyntax/loc this-syntax 103 | (#,~>-id args . body))))])) 104 | 105 | (values (make #'~>) (make #'~>>) (make #'and~>) (make #'and~>>)))) 106 | -------------------------------------------------------------------------------- /threading-lib/threading/private/cond.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require (for-syntax racket/base 4 | syntax/parse/pre) 5 | "base.rkt") 6 | 7 | (provide when~> unless~> cond~>) 8 | 9 | (define-syntax when~> 10 | (syntax-parser 11 | [(_ val-e:expr cond-e:expr clause ...) 12 | #`(let ([tmp val-e]) 13 | #,(quasisyntax/loc this-syntax 14 | (if cond-e 15 | #,(syntax/loc this-syntax 16 | (~> tmp clause ...)) 17 | tmp)))])) 18 | 19 | (define-syntax unless~> 20 | (syntax-parser 21 | [(_ val-e:expr cond-e:expr clause ...) 22 | #`(let ([tmp val-e]) 23 | #,(quasisyntax/loc this-syntax 24 | (if cond-e 25 | tmp 26 | #,(syntax/loc this-syntax 27 | (~> tmp clause ...)))))])) 28 | 29 | (define-syntax cond~> 30 | (syntax-parser 31 | #:literals [else] 32 | [(_ val-e:expr 33 | [{~and cond-e:expr {~not else}} clause ...] ... 34 | {~optional [else else-clause ...]}) 35 | (define/syntax-parse [rhs ...] 36 | (for/list ([clauses (in-list (attribute clause))]) 37 | (quasisyntax/loc this-syntax 38 | (~> tmp #,@clauses)))) 39 | #`(let ([tmp val-e]) 40 | #,(quasisyntax/loc this-syntax 41 | (cond 42 | [cond-e rhs] ... 43 | [else #,(if (attribute else-clause) 44 | (syntax/loc this-syntax 45 | (~> tmp else-clause ...)) 46 | #'tmp)])))])) 47 | -------------------------------------------------------------------------------- /threading-lib/threading/private/extra.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require (for-syntax racket/base 4 | syntax/parse/pre) 5 | "base.rkt") 6 | 7 | (provide tee~>) 8 | 9 | (define-syntax tee~> 10 | (syntax-parser 11 | [(_ val-e:expr clause ...) 12 | #`(let ([tmp val-e]) 13 | #,(syntax/loc this-syntax 14 | (~> tmp clause ...)) 15 | tmp)])) 16 | -------------------------------------------------------------------------------- /threading-test/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define version "2.0") 4 | 5 | (define license 'ISC) 6 | 7 | (define collection 'multi) 8 | 9 | (define deps 10 | '()) 11 | (define build-deps 12 | '("base" 13 | "rackunit-lib" 14 | "threading-lib")) 15 | -------------------------------------------------------------------------------- /threading-test/tests/threading/cond.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require rackunit 4 | threading) 5 | 6 | (check-equal? (when~> 1 #t add1 (* 2)) 4) 7 | (check-equal? (when~> 1 #f add1 (* 2)) 1) 8 | (check-equal? (unless~> 1 #t add1 (* 2)) 1) 9 | (check-equal? (unless~> 1 #f add1 (* 2)) 4) 10 | 11 | (let () 12 | (define (f n) 13 | (~> 11 14 | (cond~> 15 | [(= n 1) add1 (* 2)] 16 | [(= n 2) sub1 (/ 2)] 17 | [else -]))) 18 | 19 | (check-equal? (f 1) 24) 20 | (check-equal? (f 2) 5) 21 | (check-equal? (f 3) -11)) 22 | 23 | (check-equal? (cond~> 1 [#f add1]) 1) 24 | -------------------------------------------------------------------------------- /threading-test/tests/threading/extra.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require rackunit 4 | threading) 5 | 6 | (test-begin 7 | (define x 0) 8 | (define result 9 | (~> 10 10 | (+ 5) 11 | (tee~> - (set! x _)) 12 | (* 2))) 13 | 14 | (check-equal? x -15) 15 | (check-equal? result 30)) 16 | -------------------------------------------------------------------------------- /threading-test/tests/threading/main.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require (prefix-in r: racket/base) 4 | rackunit 5 | threading) 6 | 7 | (check-equal? (~> 'x) 'x) 8 | (check-equal? (~>> 'x) 'x) 9 | 10 | (check-equal? (~> 3 add1 (- 2)) 2) 11 | (check-equal? (~>> 3 add1 (- 2)) -2) 12 | 13 | (check-equal? (~> 3 add1 (- 2 _)) -2) 14 | (check-equal? (~>> 3 add1 (- _ 2)) 2) 15 | 16 | (check-equal? (~> void (_)) (void)) 17 | (check-equal? (~> add1 (_ 1)) 2) 18 | 19 | (check-equal? (and~> 'x) 'x) 20 | (check-equal? (and~>> 'x) 'x) 21 | 22 | (check-equal? (and~> #f string->number) #f) 23 | (check-equal? (and~>> #f string->number) #f) 24 | 25 | (check-equal? (and~> '(1 3 5) (findf odd? _) add1) 2) 26 | (check-equal? (and~>> '(1 3 5) (findf odd?) add1) 2) 27 | 28 | (check-equal? (and~> '(1 3 5) (findf even? _) add1) #f) 29 | (check-equal? (and~>> '(1 3 5) (findf even?) add1) #f) 30 | 31 | (test-case 32 | "Don't thread into quoted forms" 33 | 34 | (check-equal? (syntax->datum (expand-syntax-to-top-form #'(~> b 'a))) '('a b)) 35 | (check-equal? (syntax->datum (expand-syntax-to-top-form #'(~>> b 'a))) '('a b))) 36 | 37 | (test-case 38 | "Use the #%app from the surrounding lexical context" 39 | 40 | (check-equal? (let-syntax ([#%app (syntax-rules () [(_ . rest) (list . rest)])]) 41 | (~> 1 (2) (3))) 42 | '(3 (2 1))) 43 | (check-equal? (let-syntax ([#%app (syntax-rules () [(_ . rest) (list . rest)])]) 44 | (~>> 1 (2) (3))) 45 | '(3 (2 1))) 46 | (check-equal? (let-syntax ([#%app (syntax-rules () [(_ . rest) (list . rest)])]) 47 | (and~> 1 (2) (3))) 48 | '(3 (2 1))) 49 | (check-equal? (let-syntax ([#%app (syntax-rules () [(_ . rest) (list . rest)])]) 50 | (and~>> 1 (2) (3))) 51 | '(3 (2 1))) 52 | 53 | (let ([->proc (λ (x) (if (symbol? x) (λ (hsh) (hash-ref hsh x)) x))] 54 | [h (hasheq 'x (hasheq 'y 1))]) 55 | (let-syntax ([#%app (syntax-rules () [(_ x . rest) (r:#%app (->proc x) . rest)])]) 56 | (check-equal? (~> h 'x 'y) 1) 57 | (check-equal? (~>> h 'x 'y) 1) 58 | (check-equal? (and~> h 'x 'y) 1) 59 | (check-equal? (and~>> h 'x 'y) 1) 60 | (check-equal? ((λ~> 'x 'y) h) 1) 61 | (check-equal? ((λ~>> 'x 'y) h) 1) 62 | (check-equal? ((λ-and~> 'x 'y) h) 1) 63 | (check-equal? ((λ-and~>> 'x 'y) h) 1)))) 64 | -------------------------------------------------------------------------------- /threading/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define version "2.0") 4 | 5 | (define license 'ISC) 6 | 7 | (define collection 'multi) 8 | 9 | (define deps 10 | '(["threading-doc" #:version "2.0"] 11 | ["threading-lib" #:version "2.0"])) 12 | (define build-deps 13 | '()) 14 | 15 | (define implies 16 | '("threading-doc" 17 | "threading-lib")) 18 | --------------------------------------------------------------------------------