├── .editorconfig
├── .gitignore
├── additional-feature-ba.md
├── additional-feature-bc.md
├── additional-feature-bp.md
├── additional-feature-np.md
├── additional-feature-pf.md
├── additional-feature-ts.md
├── core-real-examples.md
├── core-syntax.md
├── goals.md
├── license.md
├── nomenclature.md
├── package-lock.json
├── package.json
├── readme.md
├── relations.md
├── spec.html
└── term-rewriting.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /out/
3 | /npm-debug.log
4 |
5 | deploy_key
6 |
--------------------------------------------------------------------------------
/additional-feature-ba.md:
--------------------------------------------------------------------------------
1 | |Name | Status | Features | Purpose |
2 | | ----------------------- | ------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
3 | |[Core Proposal][] | Stage 0 | Infix pipelines `… \|> …`
Lexical topic `#` | **Unary** function/expression **application** |
4 | |[Additional Feature BC][]| None | Bare constructor calls `… \|> new …` | Tacit application of **constructors** |
5 | |[Additional Feature BA][]| None | Bare awaited calls `… \|> await …` | Tacit application of **async functions** |
6 | |[Additional Feature BP][]| None | Block pipeline steps `… \|> {…}` | Application of **statement blocks** |
7 | |[Additional Feature PF][]| None | Pipeline functions `+> ` | **Partial** function/expression **application**
Function/expression **composition**
**Method extraction** |
8 | |[Additional Feature TS][]| None | Pipeline `try` statements | Tacit application to **caught errors** |
9 | |[Additional Feature NP][]| None | N-ary pipelines `(…, …) \|> …`
Lexical topics `##`, `###`, and `...`| **N-ary** function/expression **application** |
10 |
11 | # Additional Feature BA
12 | ECMAScript No-Stage Proposal. Living Document. J. S. Choi, 2018-12.
13 |
14 | This document is not yet intended to be officially proposed to TC39 yet; it merely shows a possible
15 | extension of the [Core Proposal][] in the event that the Core Proposal is accepted.
16 |
17 | An additional feature – **bare awaited calls** – would make async function calls terser. It adds
18 | another mode to bare style: if a bare-style pipeline step is preceded by a
19 | `await`, then instead of a mere function call, it is an awaited function call.
20 | `value |> await object.asyncFunction` is equivalent to `await
21 | object.asyncFunction(value)`. This would be backwards compatible with the [Core
22 | Proposal][] as well as all other [additional features][].
23 |
24 | [Additional Feature BA is **formally specified in in the draft
25 | specification**][formal BC].
26 |
27 |
With smart pipelines 31 | | Status quo 32 | 33 | |
---|---|
36 | 37 | ```js 38 | value 39 | |> # + '!' 40 | |> new User.Message(#) 41 | |> await stream.write 42 | |> console.log; 43 | ``` 44 | 45 | | 46 | 47 | ```js 48 | console.log( 49 | await stream.write( 50 | new User.Message( 51 | value + '!' 52 | ) 53 | ) 54 | ); 55 | ``` 56 | 57 | |
With smart pipelines 31 | | Status quo 32 | 33 | |
---|---|
36 | 37 | ```js 38 | value 39 | |> # + '!' 40 | |> new User.Message 41 | |> await stream.write(#) 42 | |> console.log; 43 | ``` 44 | 45 | | 46 | 47 | ```js 48 | console.log( 49 | await stream.write( 50 | new User.Message( 51 | value + '!' 52 | ) 53 | ) 54 | ); 55 | ``` 56 | 57 | |
With smart pipelines 46 | | Status quo 47 | 48 | |
---|---|
51 | 52 | ```js 53 | x = input 54 | |> f 55 | |> { sideEffect(); #; } 56 | |> g; 57 | ``` 58 | Side effects may easily be embedded within block pipeline steps. 59 | 60 | | 61 | 62 | ```js 63 | const $ = f(input); 64 | sideEffect(); 65 | x = g($); 66 | ``` 67 | 68 | |
70 | 71 | ```js 72 | x = input 73 | |> f 74 | |> { 75 | if (typeof # === 'number') 76 | # + 1; 77 | else 78 | { data: # }; 79 | } 80 | |> g; 81 | ``` 82 | `if` `else` statements may also be used within block pipeline steps, as an 83 | alternative to the ternary conditional operator `?` `:`. 84 | 85 | | 86 | 87 | ```js 88 | const _1 = f(input); 89 | let _2; 90 | if (typeof $ === 'number') 91 | _2 = $ + 1; 92 | else 93 | _2 = { data: $ }; 94 | x = g(_2); 95 | ``` 96 | 97 | |
99 | 100 | ```js 101 | x = input 102 | |> f 103 | |> { 104 | try { 105 | #|> JSON.parse; 106 | catch (error) { 107 | { message: error.message }; 108 | } 109 | } 110 | } 111 | |> g; 112 | ``` 113 | `try` statements would also be useful to embed in pipelines with block steps. 114 | This example becomes even pithier with [Additional Feature TS][]. 115 | 116 | | 117 | 118 | ```js 119 | const _1 = f(input); 120 | let _2; 121 | try { 122 | _2 = JSON.parse(_1); 123 | catch (error) { 124 | _2 = { message: error.message }; 125 | } 126 | } 127 | x = g(_2); 128 | ``` 129 | 130 | |
132 | 133 | ```js 134 | input 135 | |> await f(#, 5) 136 | |> { 137 | if (x > 20) 138 | x + 30; 139 | else 140 | x - 10; 141 | } 142 | |> g; 143 | // 🚫 Syntax Error: 144 | // Pipeline step `|> { if (…) … else … }` 145 | // binds topic but contains 146 | // no topic reference. 147 | ``` 148 | The same [early error rules][] that apply to any topic-style pipeline step apply 149 | also to topic-style steps that are `do` expressions. 150 | 151 | | |
153 | 154 | As with all other [additional features][], Additional Feature BP is [forward 155 | compatible][] with the [Core Proposal][]. This compatibility includes pipeline 156 | steps that are object literals, which must be parenthesized. 157 | ```js 158 | input |> f |> { x: #, y: # } |> g; 159 | input |> f |> { if (#) { x: #, y: # }; } |> g; 160 | ``` 161 | Of course, object literals do not have to be parenthesized inside blocks. 162 | 163 | | 164 | 165 | ```j 166 | 167 | { 168 | const _1 = f(input); 169 | let _2; 170 | if ($) _2 = { x: $, y: $ }; 171 | g(_2); 172 | } 173 | ``` 174 | 175 | |
With smart pipelines 186 | | Status quo 187 | 188 | |
---|---|
192 | 193 | ```js 194 | 'https://pk.example/berlin-calling' 195 | |> await fetch(#, { mode: 'cors' }) 196 | |> ( 197 | #.headers.get('content-type') 198 | |> # && # 199 | .toLowerCase() 200 | .indexOf('application/json') 201 | >= 0 202 | ) 203 | ? # 204 | : throw new TypeError() 205 | |> await #.json() 206 | |> processJSON; 207 | ``` 208 | This pipeline version uses [Core Proposal][] syntax only. 209 | 210 | | 211 | 212 | ```js 213 | fetch('https://pk.example/berlin-calling', 214 | { mode: 'cors' } 215 | ).then(response => { 216 | if (response.headers.get('content-type') 217 | && response.headers.get('content-type') 218 | .toLowerCase() 219 | .indexOf('application/json') >= 0 220 | ) 221 | return response.json(); 222 | else 223 | throw new TypeError(); 224 | }).then(processJSON); 225 | ``` 226 | 227 | |
229 | 230 | And this pipeline version also uses [Additional Feature BP][]. This allows the 231 | use of an `if` `else` statement instead of a ternary `?` `:` expression. 232 | ```js 233 | 'https://pk.example/berlin-calling' 234 | |> await fetch(#, { mode: 'cors' }) 235 | |> { 236 | const contentTypeIsJSON = 237 | #.headers.get('content-type') 238 | |> # && # 239 | .toLowerCase() 240 | .indexOf('application/json') 241 | >= 0; 242 | if (contentTypeIsJSON) #; 243 | else throw new TypeError(); 244 | } 245 | |> await #.json() 246 | |> processJSON; 247 | ``` 248 | It also allows the judicious use of variable/constant assignment where it would 249 | make the meaning of code clearer, rather than [requiring unnecessary variables 250 | redundant with the names of functions][terse variables]. 251 | 252 | | 253 | 254 | ```js 255 | fetch('https://pk.example/berlin-calling', 256 | { mode: 'cors' } 257 | ).then(response => { 258 | if (response.headers.get('content-type') 259 | && response.headers.get('content-type') 260 | .toLowerCase() 261 | .indexOf('application/json') >= 0 262 | ) 263 | return response.json(); 264 | else 265 | throw new TypeError(); 266 | }).then(processJSON); 267 | ``` 268 | 269 | |
With smart pipelines 279 | | Status quo 280 | 281 | |
---|---|
284 | 285 | ```js 286 | match 287 | |> context[#] 288 | |> (this[match] |> isFunction) 289 | ? this[match](#); 290 | : this.attr(match, #); 291 | ``` 292 | This pipeline version uses [Core Proposal][] syntax. 293 | 294 | | 295 | 296 | ```js 297 | if (isFunction(this[match])) { 298 | this[match](context[match]); 299 | } else 300 | this.attr(match, context[match]); 301 | } 302 | ``` 303 | From [jquery/src/core/init.js][]. 304 | 305 | |
307 | 308 | ```js 309 | match 310 | |> context[#] 311 | |> { 312 | if (this[match] |> isFunction) 313 | this[match](#); 314 | else 315 | this.attr(match, #); 316 | } 317 | ``` 318 | With [Additional Feature BP][], an `if` `else` statement can be used instead of 319 | a ternary `?` `:` expression. 320 | 321 | | 322 | 323 | ```js 324 | if (isFunction(this[match])) { 325 | this[match](context[match]); 326 | } else 327 | this.attr(match, context[match]); 328 | } 329 | ``` 330 | From [jquery/src/core/init.js][]. 331 | 332 | |
334 | 335 | ```js 336 | // Handle HTML strings 337 | if (…) 338 | … 339 | // Handle $(expr, $(...)) 340 | else if (!# || #.jquery) 341 | return context 342 | |> # || root 343 | |> #.find(selector); 344 | // Handle $(expr, context) 345 | else 346 | return context 347 | |> this.constructor 348 | |> #.find(selector); 349 | ``` 350 | This pipeline version uses [Core Proposal][] syntax only. Note that both 351 | statements are of the form `return context |> something |> #.find(selector)`. 352 | 353 | | 354 | 355 | ```js 356 | // Handle HTML strings 357 | if (…) { 358 | … 359 | // Handle $(expr, $(...)) 360 | } else if (!context || context.jquery) { 361 | return (context || root).find(selector); 362 | // Handle $(expr, context) 363 | } else { 364 | return this.constructor(context) 365 | .find(selector); 366 | } 367 | ``` 368 | From [jquery/src/core/init.js][]. 369 | 370 | |
372 | 373 | ```js 374 | return context 375 | |> { 376 | // Handle HTML strings 377 | if (…) 378 | … 379 | // Handle $(expr, $(...)) 380 | else if (!# || #.jquery) 381 | # || root; 382 | // Handle $(expr, context) 383 | else 384 | #|> this.constructor; 385 | } 386 | |> #.find(selector); 387 | ``` 388 | This pipeline version uses [Additional Feature BP][]. The common phrases `return 389 | context |>` and `|> #.find(selector)` have moved out of the `if` `else if` `else`, 390 | into its own statement. The `if` `else if` `else` itself was moved into a block in 391 | the middle of the new unified pipeline. This emphasizes the unity of the common 392 | path through which content data flow in this code. 393 | 394 | | 395 | 396 | ```js 397 | // Handle HTML strings 398 | if (…) { 399 | … 400 | // Handle $(expr, $(...)) 401 | } else if (!context || context.jquery) { 402 | return (context || root).find(selector); 403 | // Handle $(expr, context) 404 | } else { 405 | return this.constructor(context) 406 | .find(selector); 407 | } 408 | ``` 409 | From [jquery/src/core/init.js][]. 410 | 411 | |
413 | 414 | ```js 415 | return selector |> { 416 | if (typeof # === 'string') 417 | … 418 | else if (#|> isFunction) 419 | root.ready !== undefined 420 | ? root.ready(#) 421 | : #(jQuery); 422 | else 423 | jQuery.makeArray(#, this); 424 | }; 425 | ``` 426 | This is a example from jQuery’s codebase on which pipelines would not have been 427 | worth using without [Additional Feature BP][]. 428 | 429 | | 430 | 431 | ```js 432 | if (typeof selector === 'string') { 433 | … 434 | } else if (isFunction(selector)) { 435 | return root.ready !== undefined 436 | ? root.ready(selector) 437 | : selector(jQuery); 438 | } 439 | return jQuery.makeArray(selector, this); 440 | ``` 441 | From [jquery/src/core/access.js][]. 442 | 443 | |
With smart pipelines 451 | | Status quo 452 | 453 | |
---|---|
456 | 457 | ```js 458 | function hashGet (key) { 459 | return this.__data__ 460 | |> nativeCreate 461 | ? (#[key] 462 | |> # === HASH_UNDEFINED 463 | ? undefined : #) 464 | : hashOwnProperty.call(#, key) 465 | ? #[key] 466 | : undefined; 467 | } 468 | ``` 469 | This pipeline version uses [Core Proposal][] syntax only. 470 | 471 | | 472 | 473 | ```js 474 | function hashGet (key) { 475 | var data = this.__data__; 476 | if (nativeCreate) { 477 | var result = data[key]; 478 | return result === HASH_UNDEFINED 479 | ? undefined : result; 480 | } 481 | return hasOwnProperty.call(data, key) 482 | ? data[key] : undefined; 483 | } 484 | ``` 485 | 486 | |
488 | 489 | ```js 490 | function hashGet (key) { 491 | return this.__data__ |> { 492 | if (nativeCreate) { 493 | if (#[key] === HASH_UNDEFINED) 494 | undefined; 495 | else 496 | #; 497 | } else if (hashOwnProperty.call(#, key)) 498 | #[key]; 499 | }; 500 | } 501 | ``` 502 | This pipeline version also uses [Additional Feature BP][]. 503 | 504 | | 505 | 506 | ```js 507 | function hashGet (key) { 508 | var data = this.__data__; 509 | if (nativeCreate) { 510 | var result = data[key]; 511 | return result === HASH_UNDEFINED 512 | ? undefined : result; 513 | } 514 | return hasOwnProperty.call(data, key) 515 | ? data[key] : undefined; 516 | } 517 | ``` 518 | 519 | |
521 | 522 | ```js 523 | function mapCacheDelete (key) { 524 | const result = key 525 | |> getMapData(this, #) 526 | |> #['delete'] 527 | |> #(key); 528 | this.size -= result ? 1 : 0; 529 | return result; 530 | } 531 | ``` 532 | This pipeline version uses [Core Proposal][] syntax only. 533 | 534 | | 535 | 536 | ```js 537 | function mapCacheDelete (key) { 538 | var result = 539 | getMapData(this, key)['delete'](key); 540 | this.size -= result ? 1 : 0; 541 | return result; 542 | } 543 | ``` 544 | 545 | |
547 | 548 | ```js 549 | function mapCacheDelete (key) { 550 | return key 551 | |> getMapData(this, #) 552 | |> #['delete'] 553 | |> #(key) 554 | |> { 555 | this.size -= # ? 1 : 0; 556 | #; 557 | }; 558 | } 559 | ``` 560 | This pipeline version also uses [Additional Feature BP][]. 561 | 562 | | 563 | 564 | ```js 565 | function mapCacheDelete (key) { 566 | var result = 567 | getMapData(this, key)['delete'](key); 568 | this.size -= result ? 1 : 0; 569 | return result; 570 | } 571 | ``` 572 | 573 | |
575 | 576 | ```js 577 | function castPath (value, object) { 578 | return value |> 579 | #|> isArray 580 | ? # 581 | : (#|> isKey(#, object)) 582 | ? [#] 583 | : #|> toString |> stringToPath; 584 | } 585 | ``` 586 | This pipeline version uses [Core Proposal][] syntax only. 587 | 588 | | 589 | 590 | ```js 591 | function castPath (value, object) { 592 | if (isArray(value)) { 593 | return value; 594 | } 595 | return isKey(value, object) 596 | ? [value] 597 | : stringToPath(toString(value)); 598 | } 599 | ``` 600 | 601 | |
603 | 604 | ```js 605 | function castPath (value, object) { 606 | return value |> { 607 | if (#|> isArray) 608 | #; 609 | else if (#|> isKey(#, object)) 610 | [#]; 611 | else # 612 | |> toString |> stringToPath; 613 | }; 614 | } 615 | ``` 616 | This pipeline version also uses [Additional Feature BP][]. 617 | 618 | | 619 | 620 | ```js 621 | function castPath (value, object) { 622 | if (isArray(value)) { 623 | return value; 624 | } 625 | return isKey(value, object) 626 | ? [value] 627 | : stringToPath(toString(value)); 628 | } 629 | ``` 630 | 631 | |
With smart pipelines 53 | | Status quo 54 | 55 | |
---|---|
58 | 59 | The example below also uses [Additional Feature BP][]. 60 | 61 | The `try` clause is in the pipeline form using the [topic style][]. It applies 62 | the expression `1 / #` to the outer context’s topic (in this case, `f(value)`). 63 | 64 | The `catch` clause is also in the pipeline form using the [bare style][]. It 65 | applies the function `processError` to the caught error. 66 | 67 | ```js 68 | value 69 | |> f 70 | |> { 71 | try |> 1 / #; 72 | catch |> processError; 73 | } 74 | |> g; 75 | ``` 76 | The semicolons after `1 / #` and after `processError` are optional. There is no 77 | ASI hazard here because pipeline steps may never contain a `catch` or `finally` 78 | clause, unless the clause is inside a block. 79 | 80 | | 81 | 82 | ```js 83 | let _1; 84 | try { 85 | _1 = 1 / f(value); 86 | } 87 | catch (error) { 88 | _1 = processError(error); 89 | } 90 | g (_1, 1); 91 | ``` 92 | 93 | |
95 | 96 | ```js 97 | value 98 | |> f 99 | |> { 100 | try |> 1 / #; 101 | catch |> #.message; 102 | } 103 | |> g(#, 1); 104 | ``` 105 | Now the `catch` clause is also in topic style, applying apply `console.error` as 106 | a method call to the caught error. 107 | 108 | | 109 | 110 | ```js 111 | let _1; 112 | try { 113 | _1 = 1 / f(value); 114 | } 115 | catch (error) { 116 | _1 = error.message; 117 | } 118 | g (_1, 1); 119 | ``` 120 | 121 | |
123 | 124 | ```js 125 | value 126 | |> f 127 | |> { 128 | try 129 | |> 1 / #; 130 | catch 131 | |> #.message |> capitalize; 132 | } 133 | |> g(#, 1); 134 | ``` 135 | Pipeline `try` statements and their clauses may be chained as usual. This 136 | pipeline `catch` clause is in [topic style][] (`|> #.message`) followed by [bare 137 | style][] (`|> capitalize`). 138 | 139 | | 140 | 141 | ```js 142 | let _1; 143 | try { 144 | _1 = 1 / f(value); 145 | } 146 | catch (error) { 147 | _1 = capitalize(error.message); 148 | } 149 | g (_1, 1); 150 | ``` 151 | 152 | |
154 | 155 | This pipeline `try` statement’s `catch` clause is using the topic-block style 156 | from [Additional Feature BP][]. 157 | ```js 158 | value 159 | |> f 160 | |> { 161 | try |> 1 / #; 162 | catch |> { 163 | #.message |> capitalize; 164 | } 165 | } 166 | |> g; 167 | ``` 168 | A `|>` between `try` and its block `{ |> 1 / # }` is unnecessary, because the 169 | outer topic does not need to be rebound. However, it is necessary between 170 | `catch` and its block in order to opt into binding the topic reference to the 171 | caught errors. 172 | 173 | | 174 | 175 | ```js 176 | let _1; 177 | try { 178 | _1 = 1 / f(value); 179 | } 180 | catch (error) { 181 | _1 = capitalize(error.message); 182 | } 183 | g (_1, 1); 184 | ``` 185 | 186 | |
188 | 189 | If the developer includes the parenthesized parameter (like `(error)` in this 190 | example) or if they leave out the `|>` after the `catch`, then no topic binding 191 | is established. As per the [early error rules][] in [Core Proposal][], topic 192 | references are not allowed in regular `catch` blocks. 193 | ```js 194 | value 195 | |> f 196 | |> { 197 | try { 1 / #; } 198 | catch (error) { 199 | #.message |> capitalize; 200 | } 201 | } 202 | |> g(#, 1); 203 | // 🚫 Syntax Error: 204 | // Lexical context `catch { … }` 205 | // contains a topic reference 206 | // but has no topic binding. 207 | ``` 208 | This sort of [opt-in behavior][] is a goal of this proposal and helps ensure 209 | that the developer does not [shoot themselves in the foot][“don’t shoot me in 210 | the foot”] by accidentally using the topic value from an unexpected outer 211 | environment. 212 | 213 | | |
215 | 216 | ```js 217 | value 218 | |> f 219 | |> { 220 | try { 1 / #; } 221 | catch { 222 | #.message |> capitalize; 223 | } 224 | } 225 | |> g(#, 1); 226 | // 🚫 Syntax Error: 227 | // Lexical context `catch { … }` 228 | // contains a topic reference 229 | // but has no topic binding. 230 | ``` 231 | This opt-in behavior is mutually compatible with the proposal for [optional 232 | `catch` binding][]. 233 | 234 | | 235 | 236 | |
238 | 239 | ```js 240 | value 241 | |> f 242 | |> { 243 | try |> JSON.parse; 244 | catch |> { message: #.message }; 245 | } 246 | |> g(#, 1); 247 | ``` 248 | 249 | | 250 | 251 | ```js 252 | let _1; 253 | try { 254 | _1 = 1 / f(value); 255 | } 256 | catch (error) { 257 | _1 = { message: error.message }; 258 | } 259 | g (_1, 1); 260 | ``` 261 | 262 | |
With smart pipelines 13 | | Status quo 14 | 15 | |
---|---|
18 | 19 | ```js 20 | '/music/pk/altes-kamuffel' 21 | |> await fetch(#) 22 | |> await #.blob() 23 | |> playBlob; 24 | ``` 25 | 26 | | 27 | 28 | ```js 29 | fetch('/music/pk/altes-kamuffel') 30 | .then(res => res.blob()) 31 | .then(playBlob); 32 | ``` 33 | 34 | |
36 | [Equivalent to above.] 37 | 38 | | 39 | 40 | ```js 41 | playBlob( 42 | await ( 43 | await fetch('/music/pk/altes-kamuffel') 44 | ).blob() 45 | ); 46 | ``` 47 | 48 | |
50 | 51 | ```js 52 | 'https://example.com/' 53 | |> await fetch(#, { method: 'HEAD' }) 54 | |> #.headers.get('content-type') 55 | |> console.log; 56 | ``` 57 | 58 | | 59 | 60 | ```js 61 | fetch('https://example.com/', 62 | { method: 'HEAD' } 63 | ).then(response => 64 | console.log( 65 | response.headers.get('content-type')) 66 | ); 67 | ``` 68 | 69 | |
71 | 72 | ```js 73 | 'https://example.com/' 74 | |> await fetch(#, { method: 'HEAD' }) 75 | |> #.headers.get('content-type') 76 | |> console.log; 77 | ``` 78 | 79 | | 80 | 81 | ```js 82 | console.log( 83 | (await 84 | fetch('https://example.com/', 85 | { method: 'HEAD' } 86 | ) 87 | ).headers.get('content-type') 88 | ); 89 | ``` 90 | 91 | |
93 | [Equivalent to above.] 94 | 95 | | 96 | 97 | ```js 98 | { 99 | const url = 'https://example.com/'; 100 | const response = 101 | await fetch(url, { method: 'HEAD' }); 102 | const contentType = 103 | response.headers.get('content-type'); 104 | console.log(contentType); 105 | } 106 | ``` 107 | 108 | |
110 | 111 | ```js 112 | 'https://pk.example/berlin-calling' 113 | |> await fetch(#, { mode: 'cors' }) 114 | |> do { 115 | if (#.headers.get('content-type') 116 | && #.headers.get('content-type') 117 | .toLowerCase() 118 | .indexOf('application/json') >= 0 119 | ) 120 | return #.json(); 121 | else 122 | throw new TypeError(); 123 | } 124 | |> await #.json() 125 | |> processJSON; 126 | ``` 127 | This example uses [`do` expressions][], which come from another ES proposal, 128 | and which work well with smart pipelines--in this case to embed `if`–`else` statements. 129 | 130 | | 131 | 132 | ```js 133 | fetch('https://pk.example/berlin-calling', 134 | { mode: 'cors' } 135 | ).then(response => { 136 | if (response.headers.get('content-type') 137 | && response.headers.get('content-type') 138 | .toLowerCase() 139 | .indexOf('application/json') >= 0 140 | ) 141 | return response.json(); 142 | else 143 | throw new TypeError(); 144 | }).then(processJSON); 145 | ``` 146 | 147 | |
With smart pipelines 160 | | Status quo 161 | 162 | |
---|---|
165 | 166 | ```js 167 | return data 168 | |> buildFragment([#], context, scripts) 169 | |> #.childNodes 170 | |> jQuery.merge([], #); 171 | ``` 172 | The path that a reader’s eyes must trace while reading this pipeline moves 173 | straight down, with some movement toward the right then back: from `data` to 174 | `buildFragment` (and its arguments), then `.childNodes`, then `jQuery.merge`. 175 | Here, no one-off-variable assignment is necessary. 176 | 177 | | 178 | 179 | ```js 180 | parsed = buildFragment( 181 | [ data ], context, scripts 182 | ); 183 | return jQuery.merge( 184 | [], parsed.childNodes 185 | ); 186 | ``` 187 | From [jquery/src/core/parseHTML.js][]. In this code, the eyes first must look 188 | for `data` – then upwards to `parsed = buildFragment` (and then back for 189 | `buildFragment`’s other arguments) – then down, searching for the location of 190 | the `parsed` variable in the next statement – then right when noticing its 191 | `.childNodes` postfix – then back upward to `return jQuery.merge`. 192 | 193 | |
195 | 196 | ```js 197 | (key |> toType) === 'object'; 198 | ``` 199 | ```js 200 | key |> toType |> # === 'object'; 201 | ``` 202 | `|>` has a looser precedence than most operators, including `===`. (Only 203 | assignment operators, arrow function `=>`, yield operators, and the comma 204 | operator are any looser.) 205 | 206 | | 207 | 208 | ```js 209 | toType(key) === 'object'; 210 | ``` 211 | From [jquery/src/core/access.js][]. 212 | 213 | |
215 | 216 | ```js 217 | context = context 218 | |> # instanceof jQuery 219 | ? #[0] : #; 220 | ``` 221 | 222 | | 223 | 224 | ```js 225 | context = 226 | context instanceof jQuery 227 | ? context[0] : context; 228 | ``` 229 | From [jquery/src/core/access.js][]. 230 | 231 | |
233 | 234 | ```js 235 | context 236 | |> # && #.nodeType 237 | ? #.ownerDocument || # 238 | : document 239 | |> jQuery.parseHTML(match[1], #, true) 240 | |> jQuery.merge; 241 | ``` 242 | 243 | | 244 | 245 | ```js 246 | jQuery.merge( 247 | this, jQuery.parseHTML( 248 | match[1], 249 | context && context.nodeType 250 | ? context.ownerDocument 251 | || context 252 | : document, 253 | true 254 | ) 255 | ); 256 | ``` 257 | From [jquery/src/core/init.js][]. 258 | 259 | |
261 | 262 | ```js 263 | match 264 | |> context[#] 265 | |> (this[match] |> isFunction) 266 | ? this[match](#); 267 | : this.attr(match, #); 268 | ``` 269 | Note how, in this version, the parallelism between the two clauses is very 270 | clear: they both share the form `match |> context[#] |> something(match, #)`. 271 | 272 | | 273 | 274 | ```js 275 | if (isFunction(this[match])) { 276 | this[match](context[match]); 277 | } else 278 | this.attr(match, context[match]); 279 | } 280 | ``` 281 | From [jquery/src/core/init.js][]. Here, the parallelism between the clauses 282 | is somewhat less clear: the common expression `context[match]` is at the end 283 | of both clauses, at a different offset from the margin. 284 | 285 | |
287 | 288 | ```js 289 | elem = match[2] 290 | |> document.getElementById; 291 | ``` 292 | 293 | | 294 | 295 | ```js 296 | elem = document.getElementById(match[2]); 297 | ``` 298 | From [jquery/src/core/init.js][]. 299 | 300 | |
302 | 303 | ```js 304 | // Handle HTML strings 305 | if (…) 306 | … 307 | // Handle $(expr, $(...)) 308 | else if (!# || #.jquery) 309 | return context 310 | |> # || root 311 | |> #.find(selector); 312 | // Handle $(expr, context) 313 | else 314 | return context 315 | |> this.constructor 316 | |> #.find(selector); 317 | ``` 318 | The parallelism between the final two clauses becomes clearer here too. 319 | They both are of the form `return context |> something |> #.find(selector)`. 320 | 321 | | 322 | 323 | ```js 324 | // Handle HTML strings 325 | if (…) 326 | … 327 | // Handle $(expr, $(...)) 328 | else if (!context || context.jquery) 329 | return (context || root).find(selector); 330 | // Handle $(expr, context) 331 | else 332 | return this.constructor(context) 333 | .find(selector); 334 | ``` 335 | From [jquery/src/core/init.js][]. The parallelism is much less clear here. 336 | 337 | |
With smart pipelines 349 | | Status quo 350 | 351 | |
---|---|
354 | 355 | ```js 356 | function (obj, pred, context) { 357 | return obj 358 | |> isArrayLike 359 | |> # ? _.findIndex : _.findKey 360 | |> #(obj, pred, context) 361 | |> (# !== void 0 && # !== -1) 362 | ? obj[#] : undefined; 363 | } 364 | ``` 365 | 366 | | 367 | 368 | ```js 369 | function (obj, pred, context) { 370 | var key; 371 | if (isArrayLike(obj)) { 372 | key = _.findIndex(obj, pred, context); 373 | } else { 374 | key = _.findKey(obj, pred, context); 375 | } 376 | if (key !== void 0 && key !== -1) 377 | return obj[key]; 378 | } 379 | ``` 380 | 381 | |
383 | 384 | ```js 385 | function (obj, pred, context) { 386 | return pred 387 | |> cb 388 | |> _.negate 389 | |> _.filter(obj, #, context); 390 | } 391 | ``` 392 | 393 | | 394 | 395 | ```js 396 | function (obj, pred, context) { 397 | return _.filter(obj, 398 | _.negate(cb(pred)), 399 | context 400 | ); 401 | } 402 | ``` 403 | 404 | |
406 | 407 | ```js 408 | function ( 409 | srcFn, boundFn, ctxt, callingCtxt, args 410 | ) { 411 | if (!(callingCtxt instanceof boundFn)) 412 | return srcFn.apply(ctxt, args); 413 | var self = srcFn 414 | |> #.prototype |> baseCreate; 415 | return self 416 | |> srcFn.apply(#, args) 417 | |> _.isObject(#) ? # : self; 418 | } 419 | ``` 420 | 421 | | 422 | 423 | ```js 424 | function ( 425 | srcFn, boundFn, 426 | ctxt, callingCtxt, args 427 | ) { 428 | if (!(callingCtxt instanceof boundFn)) 429 | return srcFn.apply(ctxt, args); 430 | var self = baseCreate(srcFn.prototype); 431 | var result = srcFn.apply(self, args); 432 | if (_.isObject(result)) return result; 433 | return self; 434 | } 435 | ``` 436 | 437 | |
439 | 440 | ```js 441 | function (obj) { 442 | return obj 443 | |> # == null 444 | ? 0 445 | : #|> isArrayLike 446 | ? #|> #.length 447 | : #|> _.keys |> #.length; 448 | }; 449 | } 450 | ``` 451 | Smart pipelines make parallelism between all three clauses becomes clearer:\ 452 | `0` if it is nullish,\ 453 | `#|> #.length` if it is array-like, and\ 454 | `#|> something |> #.length` otherwise.\ 455 | (Technically, `#|> #.length` could simply be `#.length`, but it is written in 456 | this redundant form in order to emphasis its parallelism with the other branch.) 457 | 458 | [This particular example becomes even clearer][Underscore.js + CP + BP + PP] 459 | when paired with [Additional Feature BP][]. 460 | 461 | | 462 | 463 | ```js 464 | function (obj) { 465 | if (obj == null) return 0; 466 | return isArrayLike(obj) 467 | ? obj.length 468 | : _.keys(obj).length; 469 | } 470 | ``` 471 | 472 | |
With smart pipelines 487 | | Status quo 488 | 489 | |
---|---|
492 | 493 | ```js 494 | function hashGet (key) { 495 | return this.__data__ 496 | |> nativeCreate 497 | ? (#[key] === HASH_UNDEFINED 498 | ? undefined : #) 499 | : hashOwnProperty.call(#, key) 500 | ? #[key] 501 | : undefined; 502 | } 503 | ``` 504 | 505 | | 506 | 507 | ```js 508 | function hashGet (key) { 509 | var data = this.__data__; 510 | if (nativeCreate) { 511 | var result = data[key]; 512 | return result === HASH_UNDEFINED 513 | ? undefined : result; 514 | } 515 | return hasOwnProperty.call(data, key) 516 | ? data[key] : undefined; 517 | } 518 | ``` 519 | 520 | |
522 | 523 | ```js 524 | function listCacheHas (key) { 525 | return this.__data__ 526 | |> assocIndexOf(#, key) 527 | |> # > -1; 528 | } 529 | ``` 530 | 531 | | 532 | 533 | ```js 534 | function listCacheHas (key) { 535 | return assocIndexOf(this.__data__, key) 536 | > -1; 537 | } 538 | ``` 539 | 540 | |
542 | 543 | ```js 544 | function mapCacheDelete (key) { 545 | const result = key 546 | |> getMapData(this, #) 547 | |> #['delete'] 548 | |> #(key); 549 | this.size -= result ? 1 : 0; 550 | return result; 551 | } 552 | ``` 553 | 554 | | 555 | 556 | ```js 557 | function mapCacheDelete (key) { 558 | var result = 559 | getMapData(this, key)['delete'](key); 560 | this.size -= result ? 1 : 0; 561 | return result; 562 | } 563 | ``` 564 | 565 | |
567 | 568 | ```js 569 | function castPath (value, object) { 570 | return value |> 571 | #|> isArray 572 | ? # 573 | : (#|> isKey(#, object)) 574 | ? [#] 575 | : #|> toString |> stringToPath; 576 | } 577 | ``` 578 | 579 | | 580 | 581 | ```js 582 | function castPath (value, object) { 583 | if (isArray(value)) { 584 | return value; 585 | } 586 | return isKey(value, object) 587 | ? [value] 588 | : stringToPath(toString(value)); 589 | } 590 | ``` 591 | 592 | |