TypeScript中的函数式编程:高阶性别多态性

哈Ha!菜单名称是Yuri Bogomolov,您(大概)可以通过我在yutyube频道上的一系列#MonadicMondays的工作或者对Mediumdev.to的文章了解我。在Internet的俄语区域中,关于TypeScript中的函数式编程以及该语言的最佳生态系统之一-fp-ts库的信息很少,我前段时间对此作出了积极贡献。通过本文,我想开始一个有关TypeScript中FP的故事,如果Habra社区给予了积极的回应,我将继续该系列。



我认为TypeScript是JS最受欢迎的强类型超集之一,这对任何人都不会有任何启示。在启用严格的编译模式并设置了linter禁止使用后,any该语言变得适用于许多领域的工业开发-从CMS到银行和经纪软件。对于TypeScript类型系统,甚至还进行了非官方的尝试来证明Turing完整性,这允许应用高级类型级别的编程技术来通过使非法状态无法表示来确保业务逻辑的正确性。



以上所有因素推动fp-ts了意大利数学家Giulio Canti为TypeScript的功能编程创建了一个出色的库一个想要掌握它的人遇到的第一件事是对物种Kind<URI, SomeType>种类的非常具体的定义interface SomeKind<F extends URIS> {}在本文中,我想引导读者理解所有这些“复杂性”,并表明实际上所有内容都非常简单易懂-您只需要开始解决这个难题即可。



高阶分娩



对于函数式编程,JS开发人员通常会停止编写纯函数并编写简单的组合器。很少有人关注功能光学领域,几乎不可能碰到自由单调的API或递归方案。实际上,所有这些结构并不过分复杂,类型系统极大地促进了学习和理解。作为一种语言,TypeScript具有相当丰富的表达能力,但是它们有其​​自身的局限性,这很不方便-缺少性别/电影/种类。为了更加清楚,让我们看一个例子。



让我们大家提供一个熟悉且经过充分研究的数组数组就像一个列表一样,是一种表达非确定性思想的数据结构:它可以存储0到N个特定类型A的元素。此外,如果我们有形式为的函数A -> B,我们可以通过询问来``询问''该数组以应用它method .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, , , - . , :



  1. - F — , , : 'Array', 'Promise', 'Set', 'Tree' .
  2. - Kind<IdF, A>, F A: Kind<'F', A> ~ F<A>.
  3. 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.



fp-ts :





:








. , , , . , , . , , Mappable/Chainable .., — , , ? , .




All Articles