Olá, Habr! Nome do menu Ă© Yuri Bogomolov, e vocĂŞ (provavelmente) pode me conhecer pelo meu trabalho em uma sĂ©rie de #MonadicMondays twittou sobre o canal no yutyube ou artigos para MĂ©dio ou dev.to . No segmento de lĂngua russa da Internet, há muito pouca informação sobre programação funcional em TypeScript e um dos melhores ecossistemas para essa linguagem - a biblioteca fp-ts , para o ecossistema com o qual contribuĂ ativamente há algum tempo. Com este artigo, quero começar uma histĂłria sobre FP no TypeScript e, se houver uma resposta positiva da comunidade Habra, continuarei a sĂ©rie.
Não acho que será uma revelação para ninguém que o TypeScript é um dos superconjuntos mais populares de JS com tipagem forte. Depois de habilitar o modo de compilação estrito e definir o linter para proibir o uso, any
essa linguagem se torna adequada para desenvolvimento industrial em muitas áreas - de CMS a software bancário e de corretagem. Para o sistema de tipo TypeScript, houve atĂ© tentativas nĂŁo oficiais de provar a integridade de Turing, o que permite que tĂ©cnicas de programação de nĂvel de tipo avançadas sejam aplicadas para garantir a correção da lĂłgica de negĂłcios, tornando os estados ilegais irrepresentáveis.
Todos os itens acima deram impulso à criação de uma biblioteca maravilhosa para programação funcional para TypeScript - de fp-ts
autoria do matemático italiano Giulio Canti. Uma das primeiras coisas que uma pessoa que deseja dominar encontra sĂŁo as definições muito especĂficas dos tipos da espĂ©cie Kind<URI, SomeType>
ou interface SomeKind<F extends URIS> {}
. Neste artigo, quero levar o leitor a compreender todas essas "complexidades" e mostrar que na verdade tudo é muito simples e claro - você só precisa começar a desvendar esse quebra-cabeça.
Parto de ordem superior
Quando se trata de programação funcional, os desenvolvedores JS geralmente param em compor funções puras e escrever combinadores simples. Poucos olham para o territĂłrio da Ăłptica funcional e Ă© quase impossĂvel encontrar flertes com APIs freemonadic ou esquemas de recursĂŁo. Na verdade, todas essas construções nĂŁo sĂŁo excessivamente complicadas e o sistema de tipos facilita muito o aprendizado e a compreensĂŁo. O TypeScript como linguagem tem recursos expressivos bastante ricos, entretanto, eles tĂŞm seu limite, o que Ă© inconveniente - a ausĂŞncia de gĂŞneros / cinds / tipos. Para tornar isso mais claro, vejamos um exemplo.
. , , — , : 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 .., — , , ? , .