Skip to content

Instantly share code, notes, and snippets.

@danilobjr
Last active March 17, 2022 19:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danilobjr/816620dd9735981f0f71ca39924c1359 to your computer and use it in GitHub Desktop.
Save danilobjr/816620dd9735981f0f71ca39924c1359 to your computer and use it in GitHub Desktop.
Type-safe curried function with TypeScript

Type-safe curried function with TypeScript

This content was inpired by two great talks. I've put the links for them in References section.

Table of Contents

  1. Types
  2. curry() function
  3. Usage
  4. References
  5. Even BETTER Solution

Types

type AnyFunc = (...args: any[]) => any

type AllParams<T> = T extends (...args: infer A) => any ? A : never

type FirstParam<T extends any[]> = T extends [any, ...any[]]
  ? T[0]
  : never

type OtherParams<T extends any[]> = ((...things: T) => any) extends (
  first: any,
  ...others: infer R
) => any
  ? R
  : []

type Remainder<T extends AnyFunc> = T extends (
  ...args: infer A
) => infer R
  ? A extends [infer P1, infer P2]
    ? (b: P2) => R
    : A extends [infer P1, infer P2, infer P3]
    ? (b: P2) => (c: P3) => R
    : A extends [infer P1, infer P2, infer P3, infer P4]
    ? (b: P2) => (c: P3) => (d: P4) => R
    : never
  : never

type Curried<T extends AnyFunc> = T extends (...args: infer A) => infer R
  ? (a: FirstParam<A>) => Remainder<T>
  : never

curry() function

const curry = <T extends AnyFunc>(
  fn: T,
  arity = fn.length,
  ...args
): Curried<T> =>
  arity <= args.length
    ? fn(...args)
    : (...argz) => curry(fn, arity, ...args, ...argz)

Usage

const doSomething = (first: number, second: string, third: Record<string, unknown>) => {
  return 1
}

const curriedDoSomething = curry(doSomething) // <- (a: number) => (b: string) => (c: Record<string, unknown>) => number

References

This content is inspired by these two talks.

Not Your Mother's TDD: Type Driven Development in TypeScript - G Gilmour & R Gibson - NIDC2020 Lambda World 2018 - Functional Lenses in JavaScript - Flavio Corpa

Event BETTER Solution

This article (link down below) presents an even better solution. Much more complex I should say, but this is why we are here: to play with fire. So, besides the great learning resource, it includes a more flexible curry version. You could do something like:

const createPerson = (name: string, age: number, single: boolean, ...nicknames: string[]) => true
const curriedCreatePerson = curry(createPerson)

const test1 = curriedCreatePerson('Jane', 26)(true, 'JJ', 'Jini') // 👍 boolean
const test2 = curriedCreatePerson('Jane')(26, true, 'JJ', 'Jini') // 👍 boolean
const test3 = curriedCreatePerson('Jane')(26)(true, 'JJ', 123)    // 💥 error

How to master advanced TypeScript patterns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment