Skip to content

Instantly share code, notes, and snippets.

@donnut
Last active October 28, 2023 17:58
Show Gist options
  • Save donnut/fd56232da58d25ceecf1 to your computer and use it in GitHub Desktop.
Save donnut/fd56232da58d25ceecf1 to your computer and use it in GitHub Desktop.

Revisions

  1. Erwin Poeze revised this gist Oct 24, 2014. 1 changed file with 13 additions and 11 deletions.
    24 changes: 13 additions & 11 deletions currying.md
    Original file line number Diff line number Diff line change
    @@ -8,8 +8,8 @@ var res2 = add(1, 3); // => 4
    var add10To = add(10);
    var res = add10To(5); // => 15
    ```
    In the first line add is used by providing two arguments and the function can add both and return the
    result. The second part show a function `add10To` that is the result of calling `add` with only one
    In the first line `add` is used by providing two arguments and the function can add them and return the
    result. The second part shows a function `add10To` that is the result of calling `add` with only one
    argument. So `add` returns a function that expects the second argument at a later stage.

    How do we implement this currying of functions in TypeScript?
    @@ -23,16 +23,17 @@ receives only one argument. In terms of signatures, this would be:
    ```
    add:: number -> (number -> number) // calling add with one argument returns a function
    ```
    The implementation could be like this:
    The following implementation services this purpose:
    ```
    function add(x: number): (y: number) => number {
    return function(y: number): number {
    return x + y;
    }
    }
    ```
    This implementation works fine to create `add10To`, but forces us to specify `add(1)(3)` instead of
    the more appropriate `add(1, 3)`.
    This works fine to create `add10To`, but forces us to specify `add(1)(3)` instead of
    the more appropriate `add(1, 3)`. Look at the diffent ways of calling `add`. Not that
    the `res*` variables get their type set explicitly.
    ```
    var res1: Function = add(1);
    var res2: (y: number) => number = add(1);
    @@ -43,21 +44,22 @@ The last line generates a compile error:
    ```
    curry.ts(46,20): error TS2346: Supplied parameters do not match any signature of call target.
    ```
    This error message gives us a clue how to solve this problem. It says that the supplied
    Th error message gives us a clue on how to solve this problem. It says that the supplied
    parameters (1 and 3) do not match any signature of call target. Well, lets make a signature
    that does match! Even better, lets make two of them!
    that does match the called function! Even better, lets make two of them!

    ## Signatures
    Two signatures are relevant in case of the function add:
    Two signatures are relevant in case of the curried function add:
    ```
    function add(x: number): (y: number) => number;
    function add(x: number, y: number) => number;
    ```
    The first signature checks if add is called with one parameter (argument) in which case
    The first signature checks if add is called with one parameter (argument), in which case
    it returns a function that takes one argument and returns a number.
    If add is called with two arguments, the second signature applies and a number (the
    addition of the two supplied arguments) must be returned. This is how we can implement
    these two signatures.
    sum of the two supplied arguments) must be returned.

    These two signatures are implemented like:
    ```typescript
    function add(x: number, y?: number): any {
    if (y === undefined) {
  2. Erwin Poeze revised this gist Oct 24, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion currying.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    # TypeScript and currying

    In function programming you often want to apply a function in step. A simple example is a function `add`.
    In functional programming you often want to apply a function partly. A simple example is a function `add`.
    It would be nice if we could use `add` like:
    ```
    var res2 = add(1, 3); // => 4
  3. Erwin Poeze created this gist Oct 24, 2014.
    158 changes: 158 additions & 0 deletions currying.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,158 @@
    # TypeScript and currying

    In function programming you often want to apply a function in step. A simple example is a function `add`.
    It would be nice if we could use `add` like:
    ```
    var res2 = add(1, 3); // => 4
    var add10To = add(10);
    var res = add10To(5); // => 15
    ```
    In the first line add is used by providing two arguments and the function can add both and return the
    result. The second part show a function `add10To` that is the result of calling `add` with only one
    argument. So `add` returns a function that expects the second argument at a later stage.

    How do we implement this currying of functions in TypeScript?

    Lets start with the signature of `add`:
    ```
    add:: number -> number -> number
    ```
    This is straight forward, but now we need to implement it such that `add` returns a function when it
    receives only one argument. In terms of signatures, this would be:
    ```
    add:: number -> (number -> number) // calling add with one argument returns a function
    ```
    The implementation could be like this:
    ```
    function add(x: number): (y: number) => number {
    return function(y: number): number {
    return x + y;
    }
    }
    ```
    This implementation works fine to create `add10To`, but forces us to specify `add(1)(3)` instead of
    the more appropriate `add(1, 3)`.
    ```
    var res1: Function = add(1);
    var res2: (y: number) => number = add(1);
    var res3: number = add(1)(3);
    var res4: number = add(1, 3); // compile error
    ```
    The last line generates a compile error:
    ```
    curry.ts(46,20): error TS2346: Supplied parameters do not match any signature of call target.
    ```
    This error message gives us a clue how to solve this problem. It says that the supplied
    parameters (1 and 3) do not match any signature of call target. Well, lets make a signature
    that does match! Even better, lets make two of them!

    ## Signatures
    Two signatures are relevant in case of the function add:
    ```
    function add(x: number): (y: number) => number;
    function add(x: number, y: number) => number;
    ```
    The first signature checks if add is called with one parameter (argument) in which case
    it returns a function that takes one argument and returns a number.
    If add is called with two arguments, the second signature applies and a number (the
    addition of the two supplied arguments) must be returned. This is how we can implement
    these two signatures.
    ```typescript
    function add(x: number, y?: number): any {
    if (y === undefined) {
    return function(y: number) {
    return x + y;
    }
    } else {
    return x + y;
    }
    }
    ```
    When we compile this function and its two (overload) signatures it returns a function
    or a number depending on the supplied arguments of add. Note that the curried function
    has `any` as its return type. This is to make the compiler relax, which would otherwise
    have to choose between returning type `(y: number) => number` and `number`. By
    specifying `any`, the responsibility of setting the return type is left to the developer
    that defines the overload signatures.

    ## General currying
    Althought currying function add works fine, it is a lot of boilerplate to get it up and
    running. Lets try to generalish this concept of currying functions, but let us first limit
    to functions of two arguments and generalish from there.

    Functional javascript library Ramda uses the following function that resembles our previous
    curried `add` function a lot. I added the type definitions.
    ```
    function curry2(fn: Function): (a: number, b: number) => number {
    return function(a, b) {
    switch (arguments.length) {
    case 0:
    new TypeError('Function called with no arguments');
    case 1:
    return function(b: number) {
    return fn(a, b);
    };
    default:
    return fn(a, b);
    }
    };
    }
    ```
    When we curry `add` using `curry2` and run the same set of examples we get:

    ```
    function add(x: number, y: number): number {
    return x + y;
    }
    var curriedAdd = curry2(add);
    var res1: Function = curriedAdd(1); // compile error
    var res2: (y: number) => number = curriedAdd(1); // compile error
    var res3: number = curriedAdd(1)(3); // compile error
    var res4: number = curriedAdd(1, 3); // no error!!
    ```
    The three compile error are all the same, the first one being:
    ```
    curry2.ts(69,26): error TS2346: Supplied parameters do not match any signature of call target.
    ```
    (69,26) indicates the start of `curriedAdd(1)`. Like in the earliers error messages, TypeScript
    has a hard time determining what signature for curriesAdd is should us. We should help it
    with that by specifying the possible signatures as function overloads. But there is one
    complication. We are not talking about signature of `curry2` but signature of the function
    returned by `curry2`: the curried `add`. How do we specify the signature of a function
    returned by another function? We can not because it is not one signature but in our
    case it are two.

    I have not found a perfect solution to this problem. The best I have come sofar is
    to provide the signature and a fake function implementation. This fake function is
    later referenced to the curried function:
    ```
    function curriedAdd(x: number): (y: number) => number; // two overload signatures
    function curriedAdd(x: number, y: number): number; // ...
    function curriedAdd(x: number, y?: number): any {}; // fake implementation
    curriedAdd = curry2(add); // reference to curried add
    ```
    The previous compile error are gone, but a new, less sever one is returned:
    ```
    curry2.ts(100,5): error TS2364: Invalid left-hand side of assignment expression.
    ```
    The javascript that is generated by TypeScript is correct and TypeScript is capable
    to check if the types of res1 to res4 are matching. The new error is ugly however.

    ## Generic curry function
    The library Ramda we used for our currying function is written in javascript and not in
    TypeScript. We need to provide a separate type definition file to use Ramda without
    the need to add type definition in the functions themselves. This is done in the external
    d.ts file.

    In case of curry2 the separate type definition would be:
    ```
    interface RamdaStatic {
    curry2(fn: (a: T1, b: T2) => T3): Function;
    }
    ```
    Side-note: curry2 is a private function in Ramda. It is used in this example as an
    illustration only.