Dan
Dan

Reputation: 7724

Lodash Flow and TypeScript

I've been following an article about Lodash, Why using _.chain is a mistake, and it highlights that you can remove the need for chain by using Flow.

The example given is the following using chain

import _ from "lodash";

_.chain([1, 2, 3])
    .map(x => [x, x*2])
    .flatten()
    .sort()
    .value();

can be converted to the following using flow

import map from "lodash/fp/map";
import flatten from "lodash/fp/flatten";
import sortBy from "lodash/fp/sortBy";
import flow from "lodash/fp/flow";

flow(
    map(x => [x, x*2]),
    flatten,
    sortBy(x => x) 
)([1,2,3]);

However, when I implement this using TypeScript, I get the following error

Object is of type 'unknown'.ts(2571)

This error is highlighted on the x used in the map function.

Is there any way to fix this so that TypeScript knows which types it is dealing with?

Upvotes: 18

Views: 12303

Answers (4)

Geir Sagberg
Geir Sagberg

Reputation: 9821

I made a helper that lets you pass in the array as the first argument and the operators as the next arguments, which gives you full typing:

import { orderBy, map, filter, flow } from 'lodash/fp'

export function chainFlow<T, R1, R2, R3, R4, R5>(
  values: T,
  f1: (t: T) => R1,
  f2: (t: R1) => R2,
  f3: (t: R2) => R3,
  f4: (t: R3) => R4,
  f5: (t: R4) => R5
): R5
export function chainFlow<T, R1, R2, R3, R4>(
  values: T,
  f1: (t: T) => R1,
  f2: (t: R1) => R2,
  f3: (t: R2) => R3,
  f4: (t: R3) => R4
): R4
export function chainFlow<T, R1, R2, R3>(
  values: T,
  f1: (t: T) => R1,
  f2: (t: R1) => R2,
  f3: (t: R2) => R3
): R3
export function chainFlow<T, R1, R2>(
  values: T,
  f1: (t: T) => R1,
  f2: (t: R1) => R2
): R2
export function chainFlow<T, R1>(values: T, f1: (t: T) => R1): R1
export function chainFlow<T>(
  values: T,
  ...funcs: ((...args: any[]) => any)[]
): any {
  return flow(funcs)(values)
}

// Example usage
const x = [1,2,3,4]

const y = chainFlow(x, filter(x => x > 3), map(x => x + "abc"))

Full intellisense: enter image description here

Upvotes: 0

Ben Smith
Ben Smith

Reputation: 20230

As JavaScript/TypeScript is interpreted (note: The TypeScript code is transpiled to JavaScript which is then interpreted), when you use chain, typescript is aware of the type its dealing with (i.e. an array of numbers) as its comes first. However with flow, the type the flow functions are going to act on is specified last so it doesn't know what types it's going to use, hence the need of generics or another way of indicating the types that are going to be used. Also the linter generating this error is interpreting the code, so it doesn't know the types being dealt with when the input comes last when using "flow".

As you are using lodash with TypeScript, the functions you are using support generics to specify the types they are dealing with.

Hence you can state what is being inputted/outputted from the lodash functions as follows:

flow(
    map<number, [number, number]>(x => [x, x * 2]),
    flatten,
    sortBy(x => x)
)([1, 2, 3]);

or as dareka suggests:

flow(
    map((x: number) => [x, x * 2]),
    flatten,
    sortBy(x => x)
)([1, 2, 3])

But again this only works because we are indicating to the compiler what the type is that is going to be acted on.

An example of these code snippets working can be found here.

Note how the function inputs/outputs have been explicitly defined e.g. "map" indicates that the type it is going to iterate over is a number, and the result of the operation is a tuple.

Upvotes: 13

Ori Drori
Ori Drori

Reputation: 191976

The _.flow() method generates a pointfree function. This prevents typescript from inferring the type of the input.

To solve this, assign the function to a variable, and add the function's type. When the functions parameters and the output are declared, typescript can infer the internal types of the functions inside flow.

After assigning the function to a variable, you can call it (sandobx):

const fn: (numbers: number[]) => number[] = flow(
    map(x => [x, x*2]),
    flatten,
    sortBy(x => x) 
);

const result = fn([1,2,3]);

Or use type assertion if you want to invoke the function immediately (sandbox):

type Fn = (numbers: number[]) => number[];

const result = (flow(
  map(x => [x, x * 2]),
  flatten,
  sortBy(x => x)
) as Fn)([1, 2, 3]);

Upvotes: 9

dareka
dareka

Reputation: 178

I like Ben Smith's answer as it was quite educational for me. But I was going to suggest this

map((x: number) => [x, x*2])

or even

map((x: any) => [x, x*2])

for more general cases. It seems working and will get as much inference as possible if you don't want to be too strict.

Upvotes: 5

Related Questions