Hallo Habr! Der Menüname ist Yuri Bogomolov, und Sie können mich (wahrscheinlich) für meine Arbeit an einer Reihe von #MonadicMondays kennen , die auf dem Kanal auf yutyube oder Artikeln auf Medium oder dev.to getwittert wurden . Im russischsprachigen Segment des Internets gibt es nur sehr wenige Informationen zur funktionalen Programmierung in TypeScript und eines der besten Ökosysteme für diese Sprache - die fp-ts- Bibliothek , zu deren Ökosystem ich vor einiger Zeit aktiv beigetragen habe. Mit diesem Artikel möchte ich eine Geschichte über FP in TypeScript beginnen. Wenn die Habra-Community eine positive Antwort gibt, werde ich die Serie fortsetzen.
Ich denke nicht, dass es fĂĽr irgendjemanden eine Offenbarung sein wird, dass TypeScript eine der beliebtesten stark typisierten Supersets von JS ist. Nachdem der strikte Kompilierungsmodus aktiviert und der Linter so eingestellt wurde, dass die Verwendung verboten any
wird, eignet sich diese Sprache in vielen Bereichen für die industrielle Entwicklung - vom CMS bis zur Bank- und Brokerage-Software. Für das TypeScript-Typsystem gab es sogar inoffizielle Versuche, die Vollständigkeit von Turing zu beweisen, wodurch fortschrittliche Programmiertechniken auf Typebene angewendet werden können, um die Richtigkeit der Geschäftslogik sicherzustellen, indem illegale Staaten nicht darstellbar gemacht werden.
All dies gab fp-ts
dem italienischen Mathematiker Giulio Canti den Anstoß zur Schaffung einer wunderbaren Bibliothek für die funktionale Programmierung von TypeScript . Eines der ersten Dinge, auf die eine Person, die es beherrschen möchte, stößt, sind die sehr spezifischen Definitionen der Arten Kind<URI, SomeType>
oder Arten interface SomeKind<F extends URIS> {}
. In diesem Artikel möchte ich den Leser zu einem Verständnis all dieser "Komplexitäten" führen und zeigen, dass tatsächlich alles sehr einfach und klar ist - Sie müssen nur anfangen, dieses Rätsel zu lösen.
Geburt höherer Ordnung
Wenn es um funktionale Programmierung geht, hören JS-Entwickler normalerweise damit auf, reine Funktionen zu komponieren und einfache Kombinatoren zu schreiben. Nur wenige sehen sich das Gebiet der funktionalen Optik an, und es ist fast unmöglich, mit freemonadischen APIs oder Rekursionsschemata zu flirten. Tatsächlich sind alle diese Konstruktionen nicht übermäßig komplex, und das Typensystem erleichtert das Lernen und Verstehen erheblich. TypeScript als Sprache hat ziemlich reichhaltige Ausdrucksmöglichkeiten, jedoch haben sie ihre Grenzen, was unpraktisch ist - das Fehlen von Geschlechtern / Arten / Arten. Schauen wir uns zur Verdeutlichung ein Beispiel an.
. , , — , : 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 .., — , , ? , .