Last active
October 28, 2023 17:58
-
-
Save donnut/fd56232da58d25ceecf1 to your computer and use it in GitHub Desktop.
Revisions
-
Erwin Poeze revised this gist
Oct 24, 2014 . 1 changed file with 13 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 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 following implementation services this purpose: ``` function add(x: number): (y: number) => number { return function(y: number): number { return x + y; } } ``` 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. ``` 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 the called function! Even better, lets make two of them! ## Signatures 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 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 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) { -
Erwin Poeze revised this gist
Oct 24, 2014 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ # TypeScript and currying 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 -
Erwin Poeze created this gist
Oct 24, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.