简体   繁体   中英

How should the lodash flow function be typed in typescript?

lodash.flow<\/code> combines two or more functions.

How should this function be typed in typescript (the current definition just returns Function)?

I'm not sure how to extend the definition to work in all cases.

I don't believe you can write that definition.

If we look at the lodash type declaration file they don't try to express that relationship.

interface LoDashStatic {
    flow<TResult extends Function>(...funcs: Function[]): TResult;
}

But that alone isn't reason enough to discount the possibility. The authors may have just overlooked something, so let's keep thinking about it.

The relationship between an individual chain of functions is something you can represent. You've done so in your example above. You could create manual versions of that same idea for several lengths of parameters but that's because you're setting up a situation where the length of the chain is known and you can grant individual type information.

If we are to handle the case of variable length parameters, we must treat the parameters as a Collection . Because all variables must have a single (though possibly parameterized) type so too must this collection object. However, the types of the various functions do not align. (param:A) => B is not the same type as (param:B) => C and cannot be stored in the same well typed container (barring union types but those won't scale either).

In situations where you want to retain type information on a list of parameters like this you usually define the composition function on two parameters and apply it across a number of functions. This is how type information is retained in promises, for example. For this to work you still need to have each individual parameter spelled out. It just makes it so by the end you've got the proper output type. That said, in most cases this is what you want so it's all good.

If lodash were written in a well typed functional language, that flow function probably wouldn't exist. I imagine it would instead have been written as a piped composition object.

UPDATE : What do I mean when I say a "piped composition object"? Something like this, perhaps:

class FunctionComposer<T,V> {
    constructor(protected func: (param: T) => V) { }

    public compose<X>(newFunc: (param:V) => X) {
        return new FunctionComposer((x: T) => newFunc(this.func(x)));
    }
    public out() {
        return this.func;
    }
}

let composedFunc = new FunctionComposer((x: number) => x * 2)
    .compose(x => x.toString())
    .out();

// composedFunc has type (param:number) => string

here's a way to kind of do it using conditional types<\/a> :

const compose =
  <T1, T2>(f1: (p: T1) => T2) =>
  <T3>(f2: (p: T2) => T3) =>
  (p: T1) =>
    f2(f1(p));

const flow = <T1, T2, T3 = "♘", T4 = "♘">(
  f0: (p: T1) => T2,
  ...f: [] | [(p: T2) => T3] | [(p: T2) => T3, (p: T3) => T4]
): ((p: T1) => T3 extends "♘" ? T2 : T4 extends "♘" ? T3 : T4) => {
  if (f[1]) {
    return compose(compose(f0)(f[0]!))(f[1]) as any;
  }

  if (f[0]) {
    return compose(f0)(f[0]) as any;
  }

  return f0 as any;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM