Programação funcional em TypeScript: o padrão Typeclass

Artigos anteriores da série:







  1. Polimorfismo de gênero de ordem superior





No artigo anterior, descrevi como você pode emular o polimorfismo de gênero de ordem superior no TypeScript. Vamos agora dar uma olhada no que isso oferece ao programador funcional e começaremos com o padrão de classe de tipo.







O próprio conceito de uma typeclass vem de Haskell e foi proposto pela primeira vez por Philip Wadler e Stephen Blott em 1988 para implementar o polimorfismo ad hoc. Uma classe de tipo define um conjunto de funções digitadas e constantes que devem existir para cada tipo que pertence a uma determinada classe. Parece complicado no início, mas na verdade é um design bastante simples e elegante.







O que é uma classe de tipo



Imediatamente um aviso para aqueles que são bem versados ​​em Haskell ou Scala

, , . - TypeScript JavaScript , ( this



). , , GHC Core Language, .







Considere, como exemplo, uma das classes de tipo mais simples - Show



, - que define uma operação de fundição. Está definido no módulo fp-ts/lib/Show



:







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





Esta definição é lida assim: um tipo A



pertence a uma classe Show



se A



uma função for definida parashow : (a: A) => string



.







A classe de tipo é implementada da seguinte forma:







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