Bonjour, Habr! Le nom du menu est Yuri Bogomolov, et vous (probablement) peut me connaßtre pour mon travail sur une série de #MonadicMondays tweeté sur le canal sur yutyube ou des articles à moyen ou dev.to . Dans le segment russophone d'Internet, il y a trÚs peu d'informations sur la programmation fonctionnelle en TypeScript et l'un des meilleurs écosystÚmes pour ce langage - la bibliothÚque fp-ts , à l'écosystÚme duquel j'ai contribué assez activement il y a quelque temps. Avec cet article, je veux commencer une histoire sur FP dans TypeScript, et s'il y a une réponse positive de la communauté Habra, je continuerai la série.
Je ne pense pas que ce sera une révélation à qui que ce soit que TypeScript est l'un des sur-ensembles fortement typés les plus populaires de JS. AprÚs avoir activé le mode de compilation strict et paramétré le linter pour interdire l'utilisation, any
ce langage devient adaptĂ© au dĂ©veloppement industriel dans de nombreux domaines - du CMS aux logiciels bancaires et de courtage. Pour le systĂšme de type TypeScript, il y a mĂȘme eu des tentatives non officielles pour prouver l'exhaustivitĂ© de Turing, ce qui permet d'appliquer des techniques avancĂ©es de programmation au niveau du type pour garantir l'exactitude de la logique mĂ©tier en rendant les Ă©tats illĂ©gaux irreprĂ©sentables.
Tout ce qui précÚde a donné une impulsion à la création d'une merveilleuse bibliothÚque de programmation fonctionnelle pour TypeScript - fp-ts
par le mathématicien italien Giulio Canti. Une des premiÚres choses que rencontre une personne qui veut la maßtriser, ce sont les définitions trÚs spécifiques des types d'espÚces Kind<URI, SomeType>
ou interface SomeKind<F extends URIS> {}
. Dans cet article, je veux amener le lecteur à comprendre toutes ces «complexités» et montrer qu'en fait tout est trÚs simple et clair - il vous suffit de commencer à dérouler ce puzzle.
Accouchement d'un ordre supérieur
En ce qui concerne la programmation fonctionnelle, les dĂ©veloppeurs JS s'arrĂȘtent gĂ©nĂ©ralement Ă la composition de fonctions pures et Ă l'Ă©criture de simples combinateurs. Rares sont ceux qui se penchent sur le territoire de l'optique fonctionnelle, et il est presque impossible de tomber sur le flirt avec des API freemonadic ou des schĂ©mas de rĂ©cursivitĂ©. En fait, toutes ces constructions ne sont pas trop compliquĂ©es et le systĂšme de types facilite grandement l'apprentissage et la comprĂ©hension. TypeScript en tant que langage a des capacitĂ©s expressives assez riches, cependant, ils ont leur limite, ce qui est gĂȘnant - l'absence de genres / cinds / genres. Regardons un exemple pour le rendre plus clair.
. , , â , : 0 N A. , A -> B
, «» .map()
, B, , :
const as = [1, 2, 3, 4, 5, 6]; // as :: number[]
const f = (a: number): string => a.toString();
const bs = as.map(f); // bs :: string[]
console.log(bs); // => [ '1', '2', '3', '4', '5', '6' ]
. map
. , :
interface MappableArray {
readonly map: <A, B>(f: (a: A) => B) => (as: A[]) => B[];
}
. , , map
(Set
), - (Map
), , , ⊠, . , map
:
type MapForSet = <A, B>(f: (a: A) => B) => (as: Set<A>) => Set<B>;
type MapForMap = <A, B>(f: (a: A) => B) => (as: Map<string, A>) => Map<string, B>;
type MapForTree = <A, B>(f: (a: A) => B) => (as: Tree<A>) => Tree<B>;
type MapForStack = <A, B>(f: (a: A) => B) => (as: Stack<A>) => Stack<B>;
- Map
, , , .
, : Mappable
. , , . TypeScript, , - -:
interface Mappable<F> {
// Type 'F' is not generic. ts(2315)
readonly map: <A, B>(f: (a: A) => B) => (as: F<A>) => F<B>;
}
, , TypeScript , - F
. Scala F<_>
- â . , ? , « ».
, TypeScript , , «» â . â , . (pattern-matching) . , , «Definitional interpreters for higher-order programming languages», , .
, : - Mappable
, - F
, , , - . , :
- -
F
â , , :'Array', 'Promise', 'Set', 'Tree'
. - -
Kind<IdF, A>
,F
A
:Kind<'F', A> ~ F<A>
. -
Kind
-, â .
, :
interface URItoKind<A> {
'Array': Array<A>;
} // 1-: Array, Set, Tree, Promise, Maybe, Task...
interface URItoKind2<A, B> {
'Map': Map<A, B>;
} // 2-: Map, Either, Bifunctor...
type URIS = keyof URItoKind<unknown>; // - «» 1-
type URIS2 = keyof URItoKind2<unknown, unknown>; // 2-
// ,
type Kind<F extends URIS, A> = URItoKind<A>[F];
type Kind2<F extends URIS2, A, B> = URItoKind2<A, B>[F];
//
: URItoKindN
, , . TypeScript, (module augmentation). , :
type Tree<A> = ...
declare module 'my-lib/path/to/uri-dictionaries' {
interface URItoKind<A> {
'Tree': Tree<A>;
}
}
type Test1 = Kind<'Tree', string> // Tree<string>
Mappable
Mappable
- â 1- , :
interface Mappable<F extends URIS> {
readonly map: <A, B>(f: (a: A) => B) => (as: Kind<F, A>) => Kind<F, B>;
}
const mappableArray: Mappable<'Array'> = {
// `as` A[], - `Kind`:
map: f => as => as.map(f)
};
const mappableSet: Mappable<'Set'> = {
// â , ,
// ,
map: f => as => new Set(Array.from(as).map(f))
};
// , Tree â : ,
// , :
const mappableTree: Mappable<'Tree'> = {
map: f => as => {
switch (true) {
case as.tag === 'Leaf': return f(as.value);
case as.tag === 'Node': return node(as.children.map(mappableTree.map(f)));
}
}
};
, Mappable
, Functor
. T
fmap
, A => B
T<A>
T<B>
. , A => B
T
( , Reader/Writer/State).
fp-ts
, fp-ts. , : https://gcanti.github.io/fp-ts/guides/HKT.html. â fp-ts
URItoKind
/URItoKind2
/URItoKind3
, fp-ts/lib/HKT
.
- io-ts â - , TS
- parser-ts â ,
parsec
- monocle-ts â , monocle TS
- remote-data-ts â RemoteData,
- retry-ts â
- elm-ts â - Elm Architecture TS
- waveguide, matechs-effect â TS, ZIO
:
- circuit-breaker-monad â Circuit Breaker
- kleisli-ts â , ZIO
- fetcher-ts â fetch, io-ts
- alga-ts â alga TS
. , , , . , , . , , Mappable/Chainable .., â , , ? , .