Programmation fonctionnelle dans TypeScript: le modèle de classe de types

Articles précédents de la série:







  1. Polymorphisme de genre d'ordre supérieur





Dans l'article précédent, j'ai décrit comment vous pouvez émuler le polymorphisme de genre d'ordre supérieur dans TypeScript. Voyons maintenant ce que cela donne au programmeur fonctionnel, et nous allons commencer par le modèle de classe de type.







Le concept de classe de types lui-même est venu de Haskell et a été proposé pour la première fois par Philip Wadler et Stephen Blott en 1988 pour implémenter le polymorphisme ad hoc. Une classe de type définit un ensemble de fonctions et de constantes typées qui doivent exister pour chaque type appartenant à une classe donnée. Cela semble compliqué au début, mais c'est en fait un design assez simple et élégant.







Qu'est-ce qu'une classe de type



Immédiatement un avertissement pour ceux qui connaissent bien Haskell ou Scala

, , . - TypeScript JavaScript , ( this



). , , GHC Core Language, .







Prenons, Ă  titre d'exemple, l'une des classes de type les plus simples - Show



, - qui définit une opération de conversion. Il est défini dans le module fp-ts/lib/Show



:







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





Cette définition se lit comme suit: un type A



appartient Ă  une classe Show



si A



une fonction est dĂ©finie pourshow : (a: A) => string



.







La classe de type est implémentée comme suit:







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