├── README.md ├── LICENSE ├── short-cut-fusion.html └── zippers.html /README.md: -------------------------------------------------------------------------------- 1 | # FP Notes 2 | Experiments and Learnings in Functional Programming 3 | 4 | - [Short cut fusion](https://rawgit.com/davidchase/fp-notes/master/short-cut-fusion.html) posted on May 9, 2016 5 | - [Zippers] (https://rawgit.com/davidchase/fp-notes/master/zippers.html) Implementing functions w/ zip posted on June 20, 2016 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 David Chase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /short-cut-fusion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | fp-notes 8 | 9 | 10 | 309 | 310 | 433 | 434 | 435 | 436 | 437 | 438 | 439 |

Short cut fusion

440 | 441 |
442 |

Optimization techinque that allows for removal of intermediate data structures. 1

443 |
444 | 445 |
Simple Example:
446 | 447 |
const list = [1,2,3,4];
448 | 
449 | 
450 | //|- [12, 18, 24, 30] <-- [4, 6, 8, 10] <-- [2, 3, 4, 5] <-- [1, 2, 3, 4]
451 | const f = compose(map(triple), map(double), map(inc));
452 | 
453 | f(list); // [12, 18, 24, 30]
454 | 455 |

The above example is pretty straightforward we call inc, double and then triple on the 456 | given data list. For each of those function calls map creates a new list and passes it on 457 | to the next map(f). Seems a bit wasteful :(

458 | 459 |
//|- [12, 18, 24, 30] <------------------ [1, 2, 3, 4]
460 | const ƒ = map(compose(triple, double, inc));
461 | 
462 | ƒ(list); // [12, 18, 24, 30]
463 | 464 |

Some what subtle but greatly beneficial improvement is to compose the functions provided to map 465 | and then call map on the result of that accumulation. The result is the same, however instead of mapping 3x 466 | over the same list it happens once. Therefore no unnecessary data structures created between the 1st function 467 | call and the last.

468 | 469 |
More Involved Example:
470 | 471 |
const list = range(1, 10);
472 | 
473 | const f = compose(map(inc), filter(odd), take(3)); // [3, 5, 7]
474 | 475 |

Looks very similar to 1st example, given a list we

476 | 477 | 482 | 483 |

This again suffers from creating gratuitous lists in between the 1st and last function call.

484 | 485 |

Ideally we would like to achieve a composition of those functions take, filter and map and 486 | only “loop” through the data structure once.

487 | 488 |

Unfortunately due to the different function signatures, in their current form you cannot compose them together. IOW because map takes a function, filter takes a predicate function and take expects a number you cannot build a single function to accomdate all of those cases…

489 | 490 |

However if we re-define those functions in terms of something like reduce we may achieve the results 491 | we want.

492 | 493 |
const mapƒ = (fn, xs) => reduce((acc, input) => concat(acc, fn(input)), [], xs);
494 | const filterƒ = (pred, xs) => reduce((acc, input) => pred(input) ? concat(acc, pred(input)) : acc, [], xs);
495 | const takeƒ = (n, xs) => reduce((acc, input) => acc.length !== n ? concat(acc, input) : acc, [], xs);
496 | 497 |

This 1st pass shows that we are internally dependent on reduce and concat which we use to accumulate the values. This current situation does not allow us to be flexible in our usage of these new functions. IOW we cannot start with a list of values and return a single value ie: [1,2,3,4] => 10

498 | 499 |
const mapƒ = (fn, combineFn) => (acc, input) => combineFn(acc, fn(input));
500 | const filterƒ = (pred, combineFn) => (acc, input) => pred(input) ? combineFn(acc, input) : acc;
501 | const takeƒ = (n, combineFn) => (acc, input) => acc.length !== n ? combineFn(acc, input) : acc;
502 | 
503 | 504 |

In this 2nd attempt we have removed the dependency on a internal reduce function as well as a way to accumulate the values. It is now an argument called combineFn. So now we have 3 new functions that 505 | have a way to accumulate values and a reducing function with the signature (acc, input) which is the same as the 1st argument to reduce.

506 | 507 |
reduce(mapƒ(add(10), concat), [], range(1, 5)); // [11, 12, 13, 14]
508 | 509 |

Above we have a typical usage of the newly created mapβ using concat as the combineFn

510 | 511 |
reduce(mapƒ(inc, add), 0, range(1, 5)); // 14
512 | 513 |

In this example we use add as the accumulator (combineFn).

514 | 515 |
reduce(mapƒ(inc, filterƒ(odd, takeƒ(3, concat))), [], range(1, 10)); // [3, 5, 7]
516 | 517 |

Finally we can create a function “composition” with all of the new functions as well as a way to accumulate values. We get the same results as the example at the beginning of this section… but with a single “loop” and no intermediate data structures

518 | 519 |

Bonus:

520 | 521 |
const mapƒ = curryN(2, (fn, combineFn) => (acc, input) => combineFn(acc, fn(input)));
522 | const filterƒ = curryN(2, (pred, combineFn) => (acc, input) => pred(input) ? combineFn(acc, input) : acc);
523 | const takeƒ = curryN(2, (n, combineFn) => (acc, input) => acc.length !== n ? combineFn(acc, input) : acc);
524 | 
525 | const ƒ = compose(mapƒ(inc), filterƒ(x => x % 2 !== 0), takeƒ(3));
526 | 
527 | reduce(ƒ(concat), [], range(1, 10)) // [3, 5, 7]
528 | 529 |

As a bonus using curryN and compose from a library like ramda. We can create nice reusable compositions for our reduce function. NB typical composition is right-left but in our case the functions get applied left-right (map gets called 1st).

530 | 531 |

References

532 | 533 | 537 | 538 | 539 | 540 | 543 | 544 | 547 | 548 | 551 | 552 | 553 | 554 | 555 | 556 | -------------------------------------------------------------------------------- /zippers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Zippers 8 | 9 | 10 | 309 | 310 | 433 | 434 | 435 | 436 | 437 | 438 | 439 |

Zippers

440 | 441 |

Backstory

442 | 443 |

While working on a JavaScript application with a few colleagues using a great functional library called Ramda.

444 | 445 |

We came across a function that was in need of a bit of refactoring.

446 | 447 |

NB The following code examples are written in concise es6 format.

448 | 449 |
450 |

Original

451 |
452 | 453 |
const {any, contains} = require('ramda');
454 | const listURLs = ['http://google.com', 'http://ramdajs.com'];
455 | const urlInList = url => any(val => contains(val, url))(listURLs);
456 | 
457 | urlInList('http://google.com/images/bill+murray'); //true
458 | 459 |

The code simply checks if any provided URL string is contained within the given list of URL strings.

460 | 461 |

So why refactor? Well, in the above definition we are focusing on the specific mechanics rather than composing functions together.

462 | 463 |

High-level composition can promote a more compact and clear picture of what a function is going to do.

464 | 465 |

Below in the refactored version, we do just that:

466 | 467 |
468 |

Refactored

469 |
470 | 471 |
const {any, contains, identity, flip, useWith} = require('ramda');
472 | const listURLs = ['http://google.com', 'http://ramdajs.com'];
473 | const urlInList = useWith(any, [flip(contains), identity]);
474 | 
475 | urlInList('http://google.com/images/bill+murray', listURLs); //true
476 | 477 |

Can you spot the discrepancy?

478 | 479 | 484 | 485 |

The key function to take note of is useWith. Below is the definition from the source

486 | 487 |
488 |

useWith: accepts a function fn and a list of transformer functions and returns a new curried function. When the new function is invoked, it calls the function fn with parameters consisting of the result of calling each supplied handler on successive arguments to the new function.

489 |
490 | 491 |

A small visual… any( flip(contains)(str), identity(listURLs) )

492 | 493 | 498 | 499 |

It’s still a bit hazy so lets break it down.

500 | 501 |

To try to better understand the useWith function I decided to rebuild it using zip and you will see why in a moment.

502 | 503 |

Zip Definition

504 | 505 |

A zip takes two lists and returns a new list where the values of each list are now paired up.

506 | 507 |
const pair = (fst, snd) => [fst, snd];
508 | const map = (fn, xs) => xs.map(fn);
509 | // simple helpers
510 | 
511 | const zip = (xs, list) => map((x, idx) => pair(x, list[idx]), xs);
512 | //define a zip based on the above `map` & `pair` functions
513 | 
514 | zip([1, 2, 3], ['a', 'b', 'c']); //=> [[1, 'a'], [2, 'b'], [3, 'c']]
515 | 516 |

So far pretty straightforward, moving on:

517 | 518 |
const zipWith  = (fn, xs, arr) => map((x, i) => fn.apply(fn, pair(x, arr[i])), xs);
519 | 
520 | zipWith(f, [1, 2, 3], ['a', 'b', 'c']) // [f(1, 'a'), f(2, 'b'), f(3, 'c')]
521 | 
522 | zipWith((x, y) => [x, y], [1, 2, 3], ['a', 'b', 'c']) 
523 | // = zip([1, 2, 3], ['a', 'b', 'c']) => [[1, 'a'], [2, 'b'], [3, 'c']]
524 | 525 |

What do we have so far?

526 | 527 | 532 | 533 |

Lets keep going!

534 | 535 |
const inc = num => num + 1;
536 | const addTen = num => num + 10;
537 | const square = num => num * n;
538 | const invoke = (fn, ...args) => fn(...args);
539 | 
540 | zipWith(invoke, [inc, addTen, square], [2, 4, 6])
541 | // => [3, 14, 36]
542 | 
543 | [invoke(inc, 2), invoke(addTen, 4), invoke(square, 6)] //=> [3, 14, 36]
544 | 545 |

Here we are simply zipping a list of functions with a list of values.

546 | 547 |

The pair of functions and values get passed as arguments to invoke.

548 | 549 |

Lets further expand on the above function:

550 | 551 |
const max = Math.max;
552 | const zipThen = (after, xs, list) => after.apply(after, zipWith(invoke, xs, list));
553 | 
554 | zipThen(max, [inc, addTen, square], [2, 4, 6]) //=> 36
555 | 
556 | max(...[invoke(inc, 2), invoke(addTen, 4), invoke(square, 6)]) //=> 36
557 | 558 |

We took our zipWith(invoke) and added a function to be called afterwards.

559 | 560 |

So 1st we zip list of functions with list of values and then we call max on the resulting list.

561 | 562 |

Flashback Time

563 | 564 |
useWith(max, [inc, addTen, square])(2, 4, 6) //=> 36
565 | 
566 | zipThen(max, [inc, addTen, square], [2, 4, 6]) //=> 36
567 | 568 |

The signatures aren’t identical but that shouldn’t stop us!

569 | 570 |

Lets adjust the function definition:

571 | 572 |
const zipThen = (after, xs) => (...list) => after.apply(after, zipWith(invoke, xs, list));
573 | 
574 | zipThen(max, [inc, addTen, square])(2, 4, 6) //=> 36
575 | 
576 | useWith(max, [inc, addTen, square])(2, 4, 6) //=> 36
577 | 578 |

I don’t know about you guys but I kinda like zipThen feels a bit more descriptive. ;)

579 | 580 |

Footnotes

581 | 582 |
583 |
584 |
    585 | 586 |
  1. 587 |

    a fipped function simply means we have reversed its arguments from contains(needle, haystack) to contains(haystack, needle) 

    588 |
  2. 589 | 590 |
591 |
592 | 593 | 594 | 595 | 598 | 599 | 602 | 603 | 606 | 607 | 608 | 609 | 610 | 611 | --------------------------------------------------------------------------------