Pemrograman Fungsional dalam TypeScript: Pola Kelas Tipe

Artikel sebelumnya dalam seri:







  1. Polimorfisme genus orde tinggi





Pada artikel sebelumnya, saya menjelaskan bagaimana Anda dapat meniru polimorfisme genus tingkat tinggi di TypeScript. Sekarang mari kita lihat apa yang diberikan ini kepada programmer fungsional, dan kita akan mulai dengan pola kelas tipe.







Konsep kelas tipe berasal dari Haskell dan pertama kali diusulkan oleh Philip Wadler dan Stephen Blott pada tahun 1988 untuk mengimplementasikan polimorfisme ad hoc. Kelas tipe mendefinisikan satu set fungsi dan konstanta yang diketik yang harus ada untuk setiap tipe yang dimiliki kelas tertentu. Kedengarannya rumit pada awalnya, tetapi sebenarnya desainnya cukup sederhana dan elegan.







Apa itu kelas tipe



Segera pelepasan tanggung jawab hukum bagi mereka yang berpengalaman dalam Haskell atau Scala

, , . - TypeScript JavaScript , ( this



). , , GHC Core Language, .







Pertimbangkan, sebagai contoh, salah satu kelas tipe paling sederhana - Show



, - yang mendefinisikan operasi pengecoran. Ini didefinisikan dalam modul fp-ts/lib/Show



:







interface Show<A> {
  readonly show: (a: A) => string;
}
      
      





Definisi ini berbunyi seperti ini: tipe A



milik kelas Show



jika A



fungsi didefinisikan untukshow : (a: A) => string



.







Kelas tipe diimplementasikan sebagai berikut:







const showString: Show<string> = {
  show: s => JSON.stringify(s)
};

const showNumber: Show<number> = {
  show: n => n.toString()
};

// ,    «»   name  age:
const showUser: Show<User> = {
  show: user => `User "${user.name}", ${user.age} years old`
};
      
      





. , Show



β€” , , β€” Show



:







//     any , ..   T  
//   Show β€”       infer.
//      T  ,   
//  Show:
const getShowTuple = <T extends Array<Show<any>>>(
  ...shows: T
): Show<{ [K in keyof T]: T[K] extends Show<infer A> ? A : never }> => ({
  show: t => `[${t.map((a, i) => shows[i].show(a)).join(', ')}]`
});
      
      





(principle of least knowledge, principle of least power) β€” , . TypeScript , .







β€” , . , , . Mappable, Functor β€” . β€” , , map



, ; β€” map



; - β€” map



. :







import { Kind } from 'fp-ts/lib/HKT';
import { Functor } from 'fp-ts/lib/Functor';
import { Show } from 'fp-ts/lib/Show';

const stringify = <F extends URIS, A>(F: Functor<F>, A: Show<A>) =>
  (structure: Kind<F, A>): Kind<F, string> => F.map(structure, A.show);
      
      





, Β« Β» , ? β€” , .







, . , , , β€” :







interface Comment {
  readonly author: string;
  readonly text: string;
  readonly createdAt: Date;
}

const comments: Comment[] = ...;

const renderComments = (comments: Comment[]): Component => <List>{comments.map(renderOneComment)}</List>;
const renderOneComment = (comment: Comment): Component => <ListItem>{comment.text} by {comment.author} at {comment.createdAt}</ListItem>
      
      





, , , , comments



.







, :







interface ToComponent<A> {
  readonly render: (element: A) => Component;
}

const commentToComponent: ToComponent<Comment> = {
  render: comment => <>{comment.text} by {comment.author} at {comment.createdAt}</>
};

const arrayToComponent = <A>(TCA: ToComponent<A>): ToComponent<Comment[]> => ({
  render: as => <List>{as.map(a => <ListItem>{TCA.render(a)}</ListItem>)}</List>
});

const treeToComponent = <A>(TCA: ToComponent<A>): ToComponent<Tree<Comment>> => ({
  render: treeA => <div class="node">
    {TCA.render(treeA.value)}
    <div class="inset-relative-to-parent">
      {treeA.children.map(treeToComponent(TCA).render)}
    </div>
  </div>
});

const renderComments = 
  <F extends URIS>(TCF: ToComponent<Kind<F, Comment>>) => 
    (comments: Kind<F, Comment>) => TCF.render(comments);

...

// -       :
const commentArray: Comment[] = getFlatComments();
renderComments(arrayToComponent(commentToComponent))(commentArray);
// ... ,     :
const commentTree: Tree<Comment> = getCommentHierarchy();
renderComments(treeToComponent(commentToComponent))(commentTree);
      
      





, TypeScript :







  1. , , , .
  2. , , «» . , /instance β€” .
  3. UPPER_SNAKE_CASE, camelCase . , , β€” $tyled_like_php, .




fp-ts



, , «» .







Functor (fp-ts/lib/Functor)



map : <A, B>(f: (a: A) => B) => (fa: F<A>) => F<B>



, :







  1. - F



    , A => B



    F<A>



    , F<B>



    .
  2. A => B



    F



    , F<A> => F<B>



    .


, , , , -. , β€” - .







:







  1. : map(id) ≑ id



  2. : map(compose(f, g)) ≑ compose(map(f), map(g))





, β€” , , , , , Functor map



.







Monad (fp-ts/lib/Monad)



, , , railway, . , . , !







Β«1-2-3Β»: 1 , 2 3 :







  1. β€” , Array, List, Tree, Option, Reader .. β€” , , .
  2. , β€” chain



    join



    , of



    :

    1. :

      of : <A>(value: A) => F<A>
      chain : <A, B>(f: (a: A) => F<B>) => (fa: F<A>) => F<B>
            
            



    2. :

      of : <A>(value: A) => F<A>
      join : <A>(ffa: F<F<A>>) => F<A>
            
            



  3. , :

    1. : chain(f)(of(a)) ≑ f(a)



    2. : chain(of)(m) ≑ m



    3. : chain(g)(chain(f)(m)) ≑ chain(x => chain(g)(f(x)))(m)





of



pure



, chain



>>=



( Β«bindΒ»):







  1. : pure a >>= f ≑ f a



  2. : m >>= pure ≑ m



  3. : (m >>= f) >>= g ≑ m >>= (\x -> f x >>= g)





, , , . : type Reader<R, A> = (env: R) => A



.







, , , . , β€” , , . , (property-based testing).







. chain



: «» F



A



, β€” , A



, B



. A



F<A>



, F



. β€” Promise<A>



, A



«» . , , .







- β€” do-, for comprehension, β€” TS . - , Do fp-ts-contrib. .







Monoid (fp-ts/lib/Monoid)



:







  1. , /unit: empty : A



  2. : combine : (left: A, right: A) => A





3 :







  1. : combine(empty, x) ≑ x



  2. : combine(x, empty) ≑ x



  3. : combine(combine(x, y), z) ≑ combine(x, combine(y, z))





? β€” , , . , Β«Monoids, monoids, monoidsΒ». Scala, β€” .










β€” , Foldable/Traversable , - ; Applicative ( , ) ; Task/TaskEither/Future , . . , .








All Articles