Skip to content

Instantly share code, notes, and snippets.

@ericelliott
Last active February 2, 2023 23:33
Show Gist options
  • Save ericelliott/ea925c58410f0ae74aef to your computer and use it in GitHub Desktop.
Save ericelliott/ea925c58410f0ae74aef to your computer and use it in GitHub Desktop.

Revisions

  1. ericelliott revised this gist Jul 13, 2020. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -16,13 +16,13 @@ function wrap (foo) {
    In a nutshell, functions have types like `f (a) -> b` which means `f` is a function which takes type `a` and returns type `b`. Here's an example that should look familiar:

    ```javascript
    let wrap = (n) => [n];
    const wrap = (n) => [n];
    ```

    This example takes a single value and wraps it with an array. Why is that useful? Because it turns out there are a ton of abstractions that can work on any type because they're really about **dealing with lists rather than dealing with individual values**. All such abstractions can be **lifted** such that they work on inputs of *any type*.


    ## Lets get some really scary words out of the way, first
    ## Let's get some really scary words out of the way, first

    Polymorphism, homomorphism, monomorphism, what is a @@!$$ morphism?

    @@ -37,7 +37,7 @@ As you should know if you read my book (["Programming JavaScript Applications"](
    What's the difference? With ad-hoc polymorphism, you tend to write code like this:

    ```javascript
    let add = (a,b) => {
    const add = (a,b) => {
    if (typeof a === 'string' || typeof b === 'string') {
    return Number(a) + Number(b);
    } else if (typeof a === 'number' && typeof b === 'number') {
    @@ -56,7 +56,7 @@ Writing a function to work for any input that supports a specific set of require
    Let's lift `add()`:

    ```javascript
    let add = (a,b) => {
    const add = (a,b) => {
    return a + b;
    };
    ```
  2. ericelliott revised this gist Mar 7, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@ function wrap (foo) {
    In a nutshell, functions have types like `f (a) -> b` which means `f` is a function which takes type `a` and returns type `b`. Here's an example that should look familiar:

    ```javascript
    let wrap = (n) => return [n];
    let wrap = (n) => [n];
    ```

    This example takes a single value and wraps it with an array. Why is that useful? Because it turns out there are a ton of abstractions that can work on any type because they're really about **dealing with lists rather than dealing with individual values**. All such abstractions can be **lifted** such that they work on inputs of *any type*.
  3. ericelliott revised this gist May 26, 2015. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -116,6 +116,8 @@ Ah, that's better, but if you're paying really close attention, maybe something

    ### What's a Functor?

    A functor is a wrapper that can apply a given function to its contents & return a new instance containing the results. e.g. JS Arrays.


    ### What's a Monad?

  4. ericelliott revised this gist Feb 9, 2015. 1 changed file with 4 additions and 3 deletions.
    7 changes: 4 additions & 3 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -104,10 +104,11 @@ let add = (a,b) => {

    let wrap = (n) => Number(n);

    let args = ['1', '2'];
    let args1 = ['1', '2'];
    let args2 = [1, 2];

    console.log( add.apply(null, args.map(wrap)) ); // 3
    console.log( add.apply(null, args.map(wrap)) ); // 3
    console.log( add.apply(null, args1.map(wrap)) ); // 3
    console.log( add.apply(null, args2.map(wrap)) ); // 3
    ```

    Ah, that's better, but if you're paying really close attention, maybe something is starting to click. This is still a little awkward, but there's a light at the end of the tunnel. Time to abandon this example and dig a little deeper.
  5. Eric Elliott revised this gist Jan 4, 2015. 1 changed file with 40 additions and 0 deletions.
    40 changes: 40 additions & 0 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -118,6 +118,46 @@ Ah, that's better, but if you're paying really close attention, maybe something

    ### What's a Monad?

    **Simple answer:** Monads are chainable operations made with composable functions.

    **Complicated answer:** I'm going to shamelessly steal this from a Stackoverflow answer that is [the clearest description of monads I have ever seen](http://stackoverflow.com/questions/44965/what-is-a-monad#10245311):

    OK, explaining "what is a monad" is a bit like saying "what is a number?" We use numbers all the time. But imagine you met someone who didn't know anything about numbers. How the heck would you explain what numbers are? And how would you even begin to describe why that might be useful?

    What is a monad? The short answer: It's a specific way of chaining operations together.

    In essence, you're writing execution steps and linking them together with the "bind function". (In Haskell, it's named >>=.) You can write the calls to the bind operator yourself, or you can use syntax sugar which makes the compiler insert those function calls for you. But either way, each step is separated by a call to this bind function.

    So the bind function is like a semicolon; it separates the steps in a process. The bind function's job is to take the output from the previous step, and feed it into the next step.

    That doesn't sound too hard, right? But there is more than one kind of monad. Why? How?

    Well, the bind function can just take the result from one step, and feed it to the next step. But if that's "all" the monad does... that actually isn't very useful. And that's important to understand: Every useful monad does something else in addition to just being a monad. Every useful monad has a "special power", which makes it unique.

    (A monad that does nothing special is called the "identity monad". Rather like the identity function, this sounds like an utterly pointless thing, yet turns out not to be... But that's another story™.)

    Basically, each monad has its own implementation of the bind function. And you can write a bind function such that it does hoopy things between execution steps. For example:

    If each step returns a success/failure indicator, you can have bind execute the next step only if the previous one succeeded. In this way, a failing step aborts the whole sequence "automatically", without any conditional testing from you. (The Failure Monad.)

    Extending this idea, you can implement "exceptions". (The Error Monad or Exception Monad.) Because you're defining them yourself rather than it being a language feature, you can define how they work. (E.g., maybe you want to ignore the first two exceptions and only abort when a third exception is thrown.)

    You can make each step return multiple results, and have the bind function loop over them, feeding each one into the next step for you. In this way, you don't have to keep writing loops all over the place when dealing with multiple results. The bind function "automatically" does all that for you. (The List Monad.)

    As well as passing a "result" from one step to another, you can have the bind function pass extra data around as well. This data now doesn't show up in your source code, but you can still access it from anywhere, without having to manually pass it to every function. (The Reader Monad.)

    You can make it so that the "extra data" can be replaced. This allows you to simulate destructive updates, without actually doing destructive updates. (The State Monad and its cousin the Writer Monad.)

    Because you're only simulating destructive updates, you can trivially do things that would be impossible with real destructive updates. For example, you can undo the last update, or revert to an older version.

    You can make a monad where calculations can be paused, so you can pause your program, go in and tinker with internal state data, and then resume it.

    You can implement "continuations" as a monad. This allows you to break people's minds!

    All of this and more is possible with monads. Of course, all of this is also perfectly possible without monads too. It's just drastically easier using monads.

    http://stackoverflow.com/questions/44965/what-is-a-monad#10245311


    ### What does Reactive Mean?

  6. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -90,6 +90,8 @@ The only trouble is, that code seems even worse than the ad-hoc version of `add(
    ```javascript
    let args = ['1', '2'];

    // Use Function.prototype.apply() to
    // apply a function to an array of arguments.
    add.apply(null, args.map(wrap));
    ```

  7. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -81,7 +81,9 @@ console.log(add(wrap('1'), wrap('2'))); // 3
    console.log(add(wrap(1), wrap(2))); // 3
    ```

    Whoah. What's this? `add(wrap('1'), wrap('2'))` Pretty simple, actually. We're taking the returned results from `wrap('1')` and `wrap('2')` and passing them into `add()` as arguments. This is called **function composition**. You've probably done it before. Now you know what to call it.
    Whoah. What's this? `add(wrap('1'), wrap('2'))`

    Pretty simple, actually. We're taking the returned results from `wrap('1')` and `wrap('2')` and passing them into `add()` as arguments. This is called **function composition**. You've probably done it before. Now you know what to call it.

    The only trouble is, that code seems even worse than the ad-hoc version of `add()`. Unless there's a neat trick up my sleeve...

  8. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -49,7 +49,7 @@ console.log(add('1', '2')); // 3
    console.log(add(1, 2)); // 3
    ```

    But that's a bit silly, isn't it? What if you could always count on the input types to do arithmatic addition the `+` operator? One way to guarantee that is to make sure that all inputs support it. In this case, support for arithmatic addition is a **requirement**.
    But that's a bit silly, isn't it? What if you could always count on the input types to do arithmatic addition with the `+` operator? One way to guarantee that is to make sure that all inputs support it. In this case, support for arithmatic addition is a **requirement**.

    Writing a function to work for any input that supports a specific set of requirements is called **lifting**. Another way to think of lifting is that you abstract away the differences between the concrete implementations of a function.

  9. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -109,13 +109,13 @@ console.log( add.apply(null, args.map(wrap)) ); // 3
    Ah, that's better, but if you're paying really close attention, maybe something is starting to click. This is still a little awkward, but there's a light at the end of the tunnel. Time to abandon this example and dig a little deeper.


    ## What's a Functor?
    ### What's a Functor?


    ## What's a Monad?
    ### What's a Monad?


    ## What does Reactive Mean?
    ### What does Reactive Mean?


    ## What is Lazy Evaluation?
    ### What is Lazy Evaluation?
  10. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@

    Functional programming gets a bad wrap about being too hard for mere mortals to comprehend. This is nonsense. The concepts are actually quite simple to grasp.

    The jargon is the hardest part. A lot of that vocabulary comes from a specialized field of mathematical study called category theory (with a liberal sprinkling of type theory and abstract algebra).
    The jargon is the hardest part. A lot of that vocabulary comes from a specialized field of mathematical study called category theory (with a liberal sprinkling of type theory and abstract algebra). This sounds a lot scarier than it is. You can do this!

    All examples using ES6 syntax. `wrap (foo) => bar` means:

    @@ -81,7 +81,9 @@ console.log(add(wrap('1'), wrap('2'))); // 3
    console.log(add(wrap(1), wrap(2))); // 3
    ```

    But that seems even worse than the ad-hoc version of add. Unless there's a neat trick up my sleeve...
    Whoah. What's this? `add(wrap('1'), wrap('2'))` Pretty simple, actually. We're taking the returned results from `wrap('1')` and `wrap('2')` and passing them into `add()` as arguments. This is called **function composition**. You've probably done it before. Now you know what to call it.

    The only trouble is, that code seems even worse than the ad-hoc version of `add()`. Unless there's a neat trick up my sleeve...

    ```javascript
    let args = ['1', '2'];
  11. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -116,4 +116,4 @@ Ah, that's better, but if you're paying really close attention, maybe something
    ## What does Reactive Mean?


    ## What is lazy evaluation?
    ## What is Lazy Evaluation?
  12. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -32,7 +32,7 @@ Category theory has fancy words for everything. Everywhere you see "object" in c

    Yeah. That explains a lot, right?

    As you should know if you read my book, a polymorphic function is a function that can take and/or return multiple types. There are two types of polymorphism you'll commonly encounter in JavaScript: ad-hoc polymorphism (avoid this one if you can), and parametric polymorphism.
    As you should know if you read my book (["Programming JavaScript Applications"](http://www.amazon.com/gp/product/1491950293?ie=UTF8&camp=213733&creative=393185&creativeASIN=1491950293&linkCode=shr&tag=ericleads-20&linkId=7UQLHWD3R3N4VDGS), O'Reilly), a polymorphic function is a function that can take and/or return multiple types. There are two types of polymorphism you'll commonly encounter in JavaScript: ad-hoc polymorphism (avoid this one if you can), and parametric polymorphism.

    What's the difference? With ad-hoc polymorphism, you tend to write code like this:

  13. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -112,3 +112,8 @@ Ah, that's better, but if you're paying really close attention, maybe something

    ## What's a Monad?


    ## What does Reactive Mean?


    ## What is lazy evaluation?
  14. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -19,7 +19,7 @@ In a nutshell, functions have types like `f (a) -> b` which means `f` is a funct
    let wrap = (n) => return [n];
    ```

    This example takes a single value and wraps it with an array. Why is that useful? Because it turns out there are a ton of abstractions that can work on any type because they're really about dealing with lists rather than dealing with individual values. All such abstractions can be **lifted** such that they work on inputs of *any type*.
    This example takes a single value and wraps it with an array. Why is that useful? Because it turns out there are a ton of abstractions that can work on any type because they're really about **dealing with lists rather than dealing with individual values**. All such abstractions can be **lifted** such that they work on inputs of *any type*.


    ## Lets get some really scary words out of the way, first
  15. Eric Elliott revised this gist Jan 2, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@

    Functional programming gets a bad wrap about being too hard for mere mortals to comprehend. This is nonsense. The concepts are actually quite simple to grasp.

    The language is the hardest part. A lot of that language comes from a specialized field of mathematical study called category theory (also type theory and abstract algebra).
    The jargon is the hardest part. A lot of that vocabulary comes from a specialized field of mathematical study called category theory (with a liberal sprinkling of type theory and abstract algebra).

    All examples using ES6 syntax. `wrap (foo) => bar` means:

  16. Eric Elliott created this gist Jan 2, 2015.
    114 changes: 114 additions & 0 deletions fp-lingo.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,114 @@
    # A Guide to Functional Programming Lingo for JavaScripters

    Functional programming gets a bad wrap about being too hard for mere mortals to comprehend. This is nonsense. The concepts are actually quite simple to grasp.

    The language is the hardest part. A lot of that language comes from a specialized field of mathematical study called category theory (also type theory and abstract algebra).

    All examples using ES6 syntax. `wrap (foo) => bar` means:

    ```javascript
    function wrap (foo) {
    bar = [foo];
    return bar;
    }
    ```

    In a nutshell, functions have types like `f (a) -> b` which means `f` is a function which takes type `a` and returns type `b`. Here's an example that should look familiar:

    ```javascript
    let wrap = (n) => return [n];
    ```

    This example takes a single value and wraps it with an array. Why is that useful? Because it turns out there are a ton of abstractions that can work on any type because they're really about dealing with lists rather than dealing with individual values. All such abstractions can be **lifted** such that they work on inputs of *any type*.


    ## Lets get some really scary words out of the way, first

    Polymorphism, homomorphism, monomorphism, what is a @@!$$ morphism?

    Category theory has fancy words for everything. Everywhere you see "object" in category theory text, think "type" (I know, confusing for computer programmers, huh?) and every time you see "morphism" think "function".

    **pause for mind explosion**

    Yeah. That explains a lot, right?

    As you should know if you read my book, a polymorphic function is a function that can take and/or return multiple types. There are two types of polymorphism you'll commonly encounter in JavaScript: ad-hoc polymorphism (avoid this one if you can), and parametric polymorphism.

    What's the difference? With ad-hoc polymorphism, you tend to write code like this:

    ```javascript
    let add = (a,b) => {
    if (typeof a === 'string' || typeof b === 'string') {
    return Number(a) + Number(b);
    } else if (typeof a === 'number' && typeof b === 'number') {
    return a + b;
    }
    };

    console.log(add('1', '2')); // 3
    console.log(add(1, 2)); // 3
    ```

    But that's a bit silly, isn't it? What if you could always count on the input types to do arithmatic addition the `+` operator? One way to guarantee that is to make sure that all inputs support it. In this case, support for arithmatic addition is a **requirement**.

    Writing a function to work for any input that supports a specific set of requirements is called **lifting**. Another way to think of lifting is that you abstract away the differences between the concrete implementations of a function.

    Let's lift `add()`:

    ```javascript
    let add = (a,b) => {
    return a + b;
    };
    ```

    Much simpler, right? But now we have a problem:

    ```javascript
    console.log(add('1', '2')); // 12
    console.log(add(1, 2)); // 3
    ```

    D'oh! Now what? Well, let's just make sure that everything we send in gets converted, first. Let's spin off that wrap function above:

    ```javascript
    let wrap = (n) => Number(n);
    ```

    Now we can do this:

    ```javascript
    console.log(add(wrap('1'), wrap('2'))); // 3
    console.log(add(wrap(1), wrap(2))); // 3
    ```

    But that seems even worse than the ad-hoc version of add. Unless there's a neat trick up my sleeve...

    ```javascript
    let args = ['1', '2'];

    add.apply(null, args.map(wrap));
    ```

    So now the full source is:

    ```javascript
    let add = (a,b) => {
    return a + b;
    };

    let wrap = (n) => Number(n);

    let args = ['1', '2'];

    console.log( add.apply(null, args.map(wrap)) ); // 3
    console.log( add.apply(null, args.map(wrap)) ); // 3
    ```

    Ah, that's better, but if you're paying really close attention, maybe something is starting to click. This is still a little awkward, but there's a light at the end of the tunnel. Time to abandon this example and dig a little deeper.


    ## What's a Functor?


    ## What's a Monad?