2
votes

Composition de fonction dans Typescript sans surcharge

Est-il possible de définir un type Typecript pour la composition de fonctions (voir flow ou pipe ) pour n'importe quel nombre d'arguments (fonctions à composer) sans écrasement mais avec la possibilité d'indiquer les types?

Sans l'inférence de type, il existe un merveilleuse réponse à ma question . p>

Hélas cette solution ne valide que la chaîne et signale les erreurs lors de la définition explicite des types:

flow(
  (x: number)=>"string",
  (y: string)=>false,
  z => {/*z is any, but should be inferred as boolean*/}
);

Mais tous les arguments sont

const badChain = flow(
  (x: number)=>"string",
  (y: string)=>false,
  (z: number)=>"oops"
); // error, boolean not assignable to number


2 commentaires

Non, c'est actuellement la seule façon de le faire


La seule amélioration pourrait être de ne pas avoir de substitutions pour les arguments multiples (supprimez les surcharges A1 ... AN ne gardez qu'un seul ensemble).


3 Réponses :


3
votes

Il n'existe aucun moyen de supprimer toutes les surcharges. La façon dont les paramètres de type R * dépendent les uns des autres n'est actuellement pas exprimable dans le système de types.

Une amélioration que nous pouvons apporter est de supprimer le besoin de surcharges en ajoutant des paramètres supplémentaires sur la première fonction (ceux qui ajoutent les paramètres de type A * ). Cela peut être fait dans la version 3.0 à l'aide de tuples dans les paramètres de repos

interface LoDashStatic {

    flow<A extends any[], R1, R2>(f1: (...a: A) => R1, f2: (a: R1) => R2): (...a: A) => R2;

    flow<A extends any[], R1, R2, R3>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (...a: A) => R3;

    flow<A extends any[], R1, R2, R3, R4>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (...a: A) => R4;

    flow<A extends any[], R1, R2, R3, R4, R5>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (...a: A) => R5;

    flow<A extends any[], R1, R2, R3, R4, R5, R6>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (...a: A) => R6;

    flow<A extends any[], R1, R2, R3, R4, R5, R6, R7>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (...a: A) => R7;

}

declare const _: LoDashStatic;

let f = _.flow((n: number, s: string) => n + s, o => o.toUpperCase()); // f: (n: number, s: string) => string


0 commentaires

0
votes

La raison pour laquelle les surcharges sont toujours nécessaires n'est peut-être pas tout à fait évidente. J'ai joué avec diverses implémentations et je suis arrivé au cœur du problème que je crois, au moins étant donné une approche, qui s'est résolue autour d'énumérer toutes les chaînes de bas en haut (ce qui est légèrement mieux que les surcharges, car vous pourriez vous référer à des niveaux inférieurs) et vous devrait toujours obtenir l'inférence de type.

const a: [(_: string) => number, (_: number) => boolean] | [(_: string) => boolean] = [x => x.length, y => true]

Les surcharges sont nécessaires juste pour détecter le tuple par sa longueur. TS peut le gérer avec des surcharges (sélectionnez la signature en fonction du nombre d'arguments), mais ne parvient pas à le faire avec des tuples simples sans signatures de fonction. C'est pourquoi y n'est pas déduit dans l'extrait de code et pourquoi les efforts visant à rendre la solution plus compacte ne peuvent pas réussir avec l'état actuel sans surcharge.

La réponse approuvée semble donc être la meilleure solution pour le moment!


0 commentaires

0
votes

Il a maintenant quatre ans, mais j'ai réussi à faire fonctionner une version typée sans surcharge:

Il y a quelques choses désagréables nécessaires:

Il y a deux raisons pour lesquelles nous avons besoin de listes de nombres:

  • Étant donné un index particulier, nous devons être en mesure de récupérer l'index précédent
  • Nous devons être en mesure de convertir les index de tuple de chaîne en indices numériques
type UnariesToPiped<F extends Unary[]> = {
   [K in keyof F]:
   K extends SNumbers[number] 
      ? K extends "0"
         ? F[K]
         : (i: ReturnType<F[PrevN<S_N<K>>]>) => ReturnType<F[S_N<K>]>
      : F[K]
}

type Pipe = <F extends Unary[]>(...funcs: UnariesToPiped<F>) => (i: ParameterUnary<F[0]>) => ReturnType<F[PrevN<F["length"]>]>

type UnariesToComposed<F extends Unary[]> = {
   [K in keyof F]:
   K extends SNumbers[number] 
      ? K extends "0"
         ? F[K]
         : (i: ParameterUnary<F[S_N<K>]>) => ParameterUnary<F[PrevN<S_N<K>>]>
      : F[K]
}

type Compose = <F extends Unary[]>(...funcs: UnariesToComposed<F>) => (i: ParameterUnary<F[PrevN<F["length"]>]>) => ReturnType<F[0]>

Quelques aides:

Pipe / Compose agit sur des fonctions unaires

// Only unary functions wanted 
type Unary = (i: any) => any;

// Get the (single) argument of a given unary function
type ParameterUnary<F extends Unary> = Parameters<F>["0"]

// ReturnType is a builtin


0 commentaires