Skip to content

Instantly share code, notes, and snippets.

@donnut donnut/currying.md
Last active Feb 17, 2020

Embed
What would you like to do?
TypeScript and currying

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

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?

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 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);
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.

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:

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.

@aindlq

This comment has been minimized.

Copy link

aindlq commented Jan 7, 2015

You can have more 'safe' curry function:

curry2<T1, T2, T3>(fn: (a: T1, b: T2) => T3): (a: T1) => (b: T2) => T3

Reads almost like in Haskell ;)

@stkb

This comment has been minimized.

Copy link

stkb commented Jan 31, 2015

Have you done much work on creating a definition file for Ramda? I've started working on one myself, but the curry function is a problem :(

@jon49

This comment has been minimized.

Copy link

jon49 commented Feb 16, 2015

Not sure if you have seen this solution that works in TypeScript 1.4.0.0 and for those that might look at this post later:

import _ = require('lodash')

interface Has {
  (property: string, object: any): boolean
  (property: string): (object: any) => boolean
}

export var has = <Has> _.curry(_.rearg(_.has, 1, 0))

Not ideal, but better than nothing!

@jon49

This comment has been minimized.

Copy link

jon49 commented Feb 17, 2015

It would be interesting to make an add-in for VS that calls a command line that would parse the curried properties for us. Continuing from the example above:

(property: string, object: any): boolean

And then it would just parse it out for us. I'll have to look into it.

@stkb

This comment has been minimized.

Copy link

stkb commented Feb 19, 2015

Thanks, yeah an interface for each arity of function is what I came up with. I'm still working on definitions for Ramda but have a long way to go... I think i'll submit what I've done to DefinitelyTyped soon

@albrow

This comment has been minimized.

Copy link

albrow commented Nov 11, 2015

Just in case anyone else comes here looking for a currying solution in typescript, I was able to create type-safe, generic curry2 and curry3 functions.

// Implementation:

interface CurriedFunction2<T1, T2, R> {
    (t1: T1): (t2: T2) => R;
    (t1: T1, t2: T2): R;
}

interface CurriedFunction3<T1, T2, T3, R> {
    (t1: T1): CurriedFunction2<T2, T3, R>;
    (t1: T1, t2: T2): (t3: T3) => R;
    (t1: T1, t2: T2, t3: T3): R;
}

function curry2<T1, T2, R>(f: (t1: T1, t2: T2) => R): CurriedFunction2<T1, T2, R> {
    function curriedFunction(t1: T1): (t2: T2) => R; 
    function curriedFunction(t1: T1, t2: T2): R;
    function curriedFunction(t1: T1, t2?: T2): any {
        switch (arguments.length) {
            case 1:
                return function(t2: T2): R {
                    return f(t1, t2);
                }
            case 2:
                return f(t1, t2);
        }
    }
    return curriedFunction;
}

function curry3<T1, T2, T3, R>(f: (t1: T1, t2: T2, t3: T3) => R): CurriedFunction3<T1, T2, T3, R> {
    function curriedFunction(t1: T1): CurriedFunction2<T2, T3, R>;
    function curriedFunction(t1: T1, t2: T2): (t3: T3) => R;
    function curriedFunction(t1: T1, t2: T2, t3: T3): R;
    function curriedFunction(t1: T1, t2?: T2, t3?: T3): any {
        switch (arguments.length) {
            case 1:
                return curry2(function(t2: T2, t3: T3): R {
                    return f(t1, t2, t3);
                });
            case 2:
                return function(t3: T3): R {
                    return f(t1, t2, t3);
                };
            case 3:
                return f(t1, t2, t3);
        }
    }
    return curriedFunction;
}

// Example Usage:

const add = curry3((a: number, b: number, c: number) => a + b + c);

// Functionally speaking, all of the following are equivalent:
console.log(add(1, 2, 3));
console.log(add(1)(2, 3));
console.log(add(1, 2)(3));
console.log(add(1)(2)(3));
// Output:
//   6

const replace = curry3((regex: RegExp, replacement: string, s: string) => {
    return s.replace(regex, replacement);
})

const replaceFooWithBar = replace(/foo/, 'bar');
console.log(replaceFooWithBar('foo is cool'));
// Output:
//   bar is cool

The compiler knows the correct types for the curried functions, even when you do partial application, and there are no compiler errors. This works in typescript 1.6, but I haven't tried with earlier versions.

The original examples you provided helped point me in the right direction. I also took a peak at lodash.d.ts from DefinitelyTyped.

@hrajchert

This comment has been minimized.

Copy link

hrajchert commented Dec 4, 2015

Is there some feature we could ask to the typescript compiler to allow a general example?

This missing feature kind of kill using typescript in a functional manner

@waldogit

This comment has been minimized.

Copy link

waldogit commented Mar 30, 2016

@albrow: I liked your solution; the obvious next goal is to get rid of the curry2 and curry3 distinction and have a nice variadic curry(). Actually it turned out to be possible by following your approach and take it a little further. The parameter counting I do, is because in the context where I evaluate the argument function f, their is no f.arguments.length() that JS can query. But the result works, just remove the 2 and 3 in your usage to test it.

interface CurriedFunction2<T1, T2, R> {
    (t1: T1): (t2: T2) => R;
    (t1: T1, t2?: T2): R;
}

interface CurriedFunction3<T1, T2, T3, R> {
    (t1: T1): CurriedFunction2<T2, T3, R>;
    (t1: T1, t2?: T2): (t3: T3) => R;
    (t1: T1, t2?: T2, t3?: T3): R;
}
interface CurriedFunction4<T1, T2, T3, T4, R> {
    (t1: T1): CurriedFunction3<T2, T3, T4, R>;
    (t1: T1, t2?: T2): CurriedFunction2<T3, T4, R>;
    (t1: T1, t2?: T2, t3?: T3): (t4: T4) => R;
    (t1: T1, t2?: T2, t3?: T3, t4?: T4): R;
}
type CurriedFunction<T1, T2, T3, T4, R> = (t1: CurriedFunction<T1, T2, T3, T4, R>| T1, t2?: T2, t3?: T3, t4?: T4) => CurriedFunction<T1, T2, T3, T4, R> | 
    CurriedFunction2<T1, T2, R> | 
    CurriedFunction3<T1, T2, T3, R> | 
    CurriedFunction4<T1, T2, T3, T4, R> | R;

export function curry2<T1, T2, T3, T4, R>(f: (t1: T1, t2?: T2, t3?: T3, t4?: T4) => R): CurriedFunction2<T1, T2, R> {
    function curriedFunction(t1: T1): (t2: T2) => R; 
    function curriedFunction(t1: T1, t2?: T2): R;
    function curriedFunction(t1: T1, t2?: T2): any {
        switch (arguments.length) {
            case 1:
                return function(t2: T2): R {
                    return f(t1, t2);
                }
            case 2:
                return f(t1, t2);
        }
    }
    return curriedFunction;
}
export function curry3<T1, T2, T3, T4, R>(f: (t1: T1, t2?: T2, t3?: T3, t4?: T4) => R): CurriedFunction3<T1, T2, T3, R> {
    function curriedFunction(t1: T1): CurriedFunction2<T2, T3, R>;
    function curriedFunction(t1: T1, t2?: T2): (t3: T3) => R;
    function curriedFunction(t1: T1, t2?: T2, t3?: T3): R;
    function curriedFunction(t1: T1, t2?: T2, t3?: T3): any {
        switch (arguments.length) {
            case 1:
                return curry2(function(t2: T2, t3?: T3): R {
                    return f(t1, t2, t3);
                });
            case 2:
                return function(t3: T3): R {
                    return f(t1, t2, t3);
                };
            case 3:
                return f(t1, t2, t3);
        }
    }
    return curriedFunction;
}
export function curry4<T1, T2, T3, T4, R>(f: (t1: T1, t2?: T2, t3?: T3, t4?: T4) => R): CurriedFunction4<T1, T2, T3, T4, R> {
    function curriedFunction(t1: T1): CurriedFunction3<T2, T3, T4, R>;
    function curriedFunction(t1: T1, t2: T2): CurriedFunction2<T3, T4, R>;
    function curriedFunction(t1: T1, t2: T2, t3: T3): (t4: T4) => R;
    function curriedFunction(t1: T1, t2: T2, t3: T3, t4: T4): R;
    function curriedFunction(t1: T1, t2?: T2, t3?: T3, t4?: T4): any {
        switch (arguments.length) {
            case 1:
                return curry3(function(t2: T2, t3?: T3, t4?: T4): R {
                    return f(t1, t2, t3, t4);
                });
            case 2:
                return curry2(function(t3: T3, t4?: T4): R {
                    return f(t1, t2, t3, t4);
                });
            case 3:
                return function(t4: T4): R {
                    return f(t1, t2, t3, t4);
                };
            case 4:
                return f(t1, t2, t3, t4);
        }
    }
    return curriedFunction;
}
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): CurriedFunction4<T1, T2, T3, T4, R>;
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): CurriedFunction3<T1, T2, T3, R>;
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): CurriedFunction2<T1, T2, R>;
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): (t1: T1, t2?: T2, t3?: T3, t4?:T4) => R;
export function curry<T1, T2, T3, T4, R>(f: CurriedFunction<T1, T2, T3, T4, R>): CurriedFunction<T1, T2, T3, T4, R> {
    function countArgs(func : string): number {
        const args = func.substring(func.indexOf('(') + 1, func.indexOf(')'));
        if (args.trim().length === 0) {
            return 0;
        }
        let pos = 0;
        let cnt = 0;
        while (pos >= 0) {
            cnt++;
            pos = args.indexOf(',', pos + 1);
        }
        return cnt;    
    }
    switch (countArgs(f.toString())) {
        case 0:
            return f;
        case 1:
            return f;
        case 2: 
            return curry2(f);
        case 3:
            return curry3(f);
        case 4: 
            return curry4(f);
        default:
            throw new SyntaxError(`More than 4 args in ${f.toString()}`);
    }
}
@donnut

This comment has been minimized.

Copy link
Owner Author

donnut commented Apr 9, 2016

Maybe I found a more compact solution for currying. It boils down to creating an interface what uses type variables for the arguments of the functions defined in the interface, but 'reuses' the type variable for the result:

    interface CurriedFunction2<T1, T2, R> {
        <T1>(t1: T1): <T2>(t2: T2) => R;
        <T1,T2>(t1: T1, t2: T2): R;
    }

T1 and T2 are repeated in the function specification, R isn't. I did the same for the higher order CurriedFunctions and the tests seem to work fine:

    interface CurriedFunction3<T1, T2, T3, R> {
        <T1>(t1: T1): CurriedFunction2<T2, T3, R>;
        <T1,T2>(t1: T1, t2: T2): <T3>(t3: T3) => R;
        <T1,T2,T3>(t1: T1, t2: T2, t3: T3): R;
    }

    interface CurriedFunction4<T1, T2, T3, T4, R> {
        <T1>(t1: T1): CurriedFunction3<T2, T3, T4, R>;
        <T1,T2>(t1: T1, t2: T2): CurriedFunction2<T3, T4, R>;
        <T1,T2,T3>(t1: T1, t2: T2, t3: T3): <T4>(t4: T4) => R;
        <T1,T2,T3,T4>(t1: T1, t2: T2, t3: T3, t4: T4): R;
    }

The tests all pass:

    var addFourNumbers = function(a: number, b: number, c: number, d: number): number {
      return a + b + c + d;
    };

    const curriedFourNumbers = R.curry(addFourNumbers);
    var x1: R.CurriedFunction4<number, number, number, number, number> = curriedFourNumbers
    var x2: R.CurriedFunction3<number, number, number, number> = curriedFourNumbers(1)
    var x3: R.CurriedFunction2<number, number, number> = curriedFourNumbers(1)(2)
    var x4: <T1,R>(t1: T1) => R = curriedFourNumbers(1)(2)(3)
    var x5: <T1,R>(t1: T1) => R = curriedFourNumbers(1,2,4)
    var y1: number = curriedFourNumbers(1)(2)(3)(4)
    var y2: number = curriedFourNumbers(1,2)(3,4)
    var y3: number = curriedFourNumbers(1,2,3)(4)
@waldogit

This comment has been minimized.

Copy link

waldogit commented Apr 16, 2016

I edited the code in my previous comment. I now added recursive types which allow me to curry over a curried function. E.g.:

const curriedAdd = curry((a:string, b:number, c:number, d:number) => parseInt(a) + b + c + d); 
it('expect curried curry4 to handle 4 + 0 args', () => {
    expect(curry(curriedAdd)('1', 2, 3, 4)).toEqual(10);
});
it('expect curried curry4 to handle 2 + 1 + 1 args', () => {
    expect(curry(curriedAdd)('1', 2)(3)(4)).toEqual(10);
});
it('expect curried curry4 to handle 1 + 1 + 1 + 1 args', () => {
    expect(curry(curriedAdd)('1')(2)(3)(4)).toEqual(10);
});

I put the code plus more tests on a gist
I kept working on that gist, and it now includes support for partially applying by passing 'gap' arguments as _, in Ramda.js style

@AlexGalays

This comment has been minimized.

Copy link

AlexGalays commented Apr 19, 2016

I don't think you can have a typesafe curry definition, and in particular when dealing with intersection types :

interface CurriedFunction2<T1, T2, R> {
    <T1>(t1: T1): <T2>(t2: T2) => R;
    <T1,T2>(t1: T1, t2: T2): R;
}

function curry2<A, B, R>(fn: (a: A, b: B) => R): CurriedFunction2<A, B, R> {
  const curried: any = function(a: A, b: B) {
    return arguments.length == 2 ? fn(a, b) : function(b: B) { return fn(a, b) }
  }
  return curried
}

function copy<T extends Object>(obj: T): T {
  let result: any = {}
  Object.keys(obj).forEach(key => result[key] = (<any>obj)[key])
  return result as T
}

function extend<T extends Object, U extends Object>(obj: T, other: U): T & U {
  let result: any = copy(obj)
  Object.keys(other).forEach(key => result[key] = (<any>other)[key])
  return result as any
}

const curriedExtend = curry2(extend)

const x = extend({ a: 1 }, { b: '2' })

const y = curriedExtend({ a: 1 }, { b: '2' })

x's type is properly inferred, y's is not. Any type relation/constraint is lost.

typesafety or currying, pick one.

@waldogit

This comment has been minimized.

Copy link

waldogit commented Apr 21, 2016

I do think you can have a typesafe curry definition, except for intersection types. The example extend() is a mixin generator i.e. a dynamic type mixer. That the extend compiles, is a favour the Typescript team is granting us to honour the dynamic spirit of javascript. So, besides that favour: dynamic types or typesafety, pick one.

@tusharmath

This comment has been minimized.

Copy link

tusharmath commented Oct 25, 2016

@donnut Is there a way to use generics instead of numbers here —

 var x1: R.CurriedFunction4<T, T, T, T, T> = curriedFourNumbers

I know this is wrong syntax but hope you get what I am looking for.

@championswimmer

This comment has been minimized.

Copy link

championswimmer commented Feb 10, 2018

variable types are landing in Typescript 2.8
That will allow us to have curryN types easily
The return type can be conditional

@paldepind

This comment has been minimized.

Copy link

paldepind commented Feb 26, 2018

Please correct me if I'm wrong, but none of the typings for curry in this thread works at all with functions that have generics. This means that the curry function only works with the simplest of functions.

function map<A>(fn: (a: A) => B, array: A[]): B[] {
  // ...
}

const curriedMap = curry2(map); // does not have proper type
@iha2

This comment has been minimized.

Copy link

iha2 commented Mar 3, 2018

@paldepind your right non of this works with any functions with generics. When working with functions with generics type any is returned. It seems the Typescript static checker gives up. See typed-typings/npm-ramda#78

@neoneo

This comment has been minimized.

Copy link

neoneo commented Mar 13, 2018

@paldepind @iha2 One interesting approach I found is to introduce an additional interface like this:

interface Map {
    <A, B>(fn: (a: A) => B, array: A[]): B[]
    <A, B>(fn: (a: A)) => B): (array: A[]) => B[]
}

and then cast to it after currying:

const curriedMap = curry2(map) as Map;

You end up with a lot of duplicated type signatures, but if it's part of a library that could be acceptable.

@wagerfield

This comment has been minimized.

Copy link

wagerfield commented Jul 11, 2018

@donnut could you please explain what the difference is between:

// @albrow's original solution
interface CurriedFunction2<T1, T2, R> {
  (t1: T1): <T2>(t2: T2) => R
  (t1: T1, t2: T2): R
}

// your "compact" solution
interface CurriedFunction2<T1, T2, R> {
  <T1>(t1: T1): <T2>(t2: T2) => R
  <T1,T2>(t1: T1, t2: T2): R
}

When I try and add the <T1, T2> generics to front of functions in the interface I get a warning from TSLint saying "Shadowed name: 'A' (no-shadowed-variable)". This is a linting error that can obviously be suppressed, but I would like to know what this adds to the solution.

Many thanks in advance!

@gtkatakura-bysoft

This comment has been minimized.

Copy link

gtkatakura-bysoft commented Sep 21, 2018

@donnut @albrow

I came in a more "generic" solution (with a little problem with undefineds) in TypeScript 3.1:

type Tail<F extends Function, S extends Number> =
  S extends 0 ? (F extends (...args: infer TArgs) => any ? TArgs : never) :
  S extends 1 ? (F extends (a: any, ...args: infer TArgs) => any ? TArgs : never) :
  S extends 2 ? (F extends (a: any, b: any, ...args: infer TArgs) => any ? TArgs : never) :
  S extends 3 ? (F extends (a: any, b: any, c: any, ...args: infer TArgs) => any ? TArgs : never) :
  S extends 4 ? (F extends (a: any, b: any, c: any, d: any, ...args: infer TArgs) => any ? TArgs : never) :
  S extends 5 ? (F extends (a: any, b: any, c: any, d: any, e: any, ...args: infer TArgs) => any ? TArgs : never) :
  S extends 6 ? (F extends (a: any, b: any, c: any, d: any, e: any, f: any, ...args: infer TArgs) => any ? TArgs : never) :
  never

type TailArray<A extends any[], S extends Number> =
  Tail<(...args: A) => any, S>

type Args<T extends Function> =
  T extends (...args: infer TArgs) => any ? TArgs
  : never

type PartialArgs<T extends Function> =
  T extends (...args: infer TArgs) => any ? Partial<TArgs>
  : never

type Curried<T extends (...args: any) => any, TReturn = ReturnType<T>> =
  <
    TArgs extends PartialArgs<T>,
    TRest extends TailArray<Args<T>, TArgs['length']>
  >(...args: TArgs) =>
    TRest extends []
      ? TReturn
      : Curried<(...args: TRest) => TReturn>

type Curry = <TFunc extends (...args: any) => any>(func: TFunc) => Curried<TFunc>

declare const curry: Curry

const curried = curry((a: 1 | undefined, b: number | number[], c: string) => 1)

// works :D
const a = curried(1)([2])('x')
const b = curried(1)(2, 'x')
const c = curried(1, 2)('x')
const d = curried(1, 2, 'x')


// the only problem is that `undefined` is accepted
// Partial<[1, 2]> => [1 | undefined, 2 | undefined]
curried(undefined)(2)(undefined)
@gtkatakura

This comment has been minimized.

Copy link

gtkatakura commented Sep 21, 2018

I got it, real variadic curry (only TS 3.1):

type Args<T extends Function> =
  T extends (...args: infer TArgs) => any ? TArgs
  : never

type TailArgs<F extends Function> =
  F extends (head: any, ...tail: infer TTail) => any ? TTail :
  never

type TailArray<A extends any[]> = TailArgs<(...args: A) => any>

type CastArray<T> = T extends any[] ? T : []

type DropFromArraySize<T extends any[], TSize extends any[]> = CastArray<{
  "end": T,
  "continue": DropFromArraySize<TailArray<T>, TailArray<TSize>>,
}[TSize extends [] ? "end" : "continue"]>

type PartialArgs<T extends Function> =
  T extends (...args: infer TArgs) => any ? Partial<TArgs>
  : never

type Curried<T extends (...args: any) => any, TReturn = ReturnType<T>> =
  <
    TInitArgs extends PartialArgs<T>,
    TTailArgs extends DropFromArraySize<Args<T>, TInitArgs>
  >(...args: TInitArgs) =>
    TTailArgs extends []
      ? TReturn
      : Curried<(...args: TTailArgs) => TReturn>

type Curry = <TFunc extends (...args: any) => any>(func: TFunc) => Curried<TFunc>

declare const curry: Curry

const curried = curry((a: 1 | undefined, b: number | number[], c: string) => 1)

// works :D
const a = curried(1)([2])('x')
const b = curried(1)(2, 'x')
const c = curried(1, 2)('x')
const d = curried(1, 2, 'x')

// the only problem is that `undefined` is accepted
// Partial<[1, 2]> => [1 | undefined, 2 | undefined]
curried(undefined)(2)(undefined)
curried(undefined)(2)('2')
@gtkatakura

This comment has been minimized.

Copy link

gtkatakura commented Sep 21, 2018

I solved the problem of undefined, here:

The changes:

// new types

type Init<T extends any[], TTail extends any[] = TailArray<T>> = CastArray<{
  [K in keyof TTail]: T[keyof T & K];
}>

type PotentialArgs<T extends any[], TResult extends any[] = T> = {
  "continue": PotentialArgs<Init<T>, TResult | Init<T>>;
  "end": TResult;
}[T extends [] ? "end" : "continue"]

// changes
type Curried<T extends (...args: any) => any> =
  <
    TInitArgs extends PotentialArgs<Args<T>>, // use here
    TTailArgs extends DropFromArraySize<Args<T>, TInitArgs>
  >(...args: TInitArgs) =>
    TTailArgs extends []
      ? ReturnType<T>
      : Curried<(...args: TTailArgs) => ReturnType<T>>
@joshburgess

This comment has been minimized.

Copy link

joshburgess commented Sep 24, 2018

@gtkatakura When I tried the above it crashed my editor, the TS compiler, and also the online TS playground.

@patrickmichalina

This comment has been minimized.

Copy link

patrickmichalina commented Sep 29, 2018

@joshburgess be sure to use TS 3.1. It appears to work so far!

@pirix-gh

This comment has been minimized.

Copy link

pirix-gh commented Mar 1, 2019

Hi everyone,

I wrote a real solution for this problem. If you would like to find out how it's done:
https://medium.freecodecamp.org/typescript-curry-ramda-types-f747e99744ab

@VulcanRav

This comment has been minimized.

Copy link

VulcanRav commented Jul 25, 2019

@pirix-gh your implementation still can't infer correctly pretty simple cases:

`
import { F } from 'ts-toolbelt'
declare function curry(fns: Fns): F.Curry

const tuple = <A, B>(a: A, b: B): [A, B] => [a, b]

// const f: F.Curry<(a: any, b: any) => [any, any]>
const f = curry(tuple)

// x: [any, any]
const x = f(1)('a')
`

@pirix-gh

This comment has been minimized.

Copy link

pirix-gh commented Jul 25, 2019

@VulcanRav, yes it is a limitation that is related to typescript.
Being discussed here microsoft/TypeScript#5453 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.