Artigos anteriores da série:
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
, , . - 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 :
- , , , .
- , , «» . , /instance — .
- 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>
, :
- -
F
,A => B
F<A>
,F<B>
. -
A => B
F
,F<A> => F<B>
.
, , , , -. , — - .
:
- :
map(id) ≡ id
- :
map(compose(f, g)) ≡ compose(map(f), map(g))
, — , , , , , Functor map
.
Monad (fp-ts/lib/Monad)
, , , railway, . , . , !
«1-2-3»: 1 , 2 3 :
- — , Array, List, Tree, Option, Reader .. — , , .
- , —
chain
join
,of
:
- :
of : <A>(value: A) => F<A> chain : <A, B>(f: (a: A) => F<B>) => (fa: F<A>) => F<B>
- :
of : <A>(value: A) => F<A> join : <A>(ffa: F<F<A>>) => F<A>
- :
- , :
- :
chain(f)(of(a)) ≡ f(a)
- :
chain(of)(m) ≡ m
- :
chain(g)(chain(f)(m)) ≡ chain(x => chain(g)(f(x)))(m)
- :
of
pure
, chain
>>=
( «bind»):
- :
pure a >>= f ≡ f a
- :
m >>= pure ≡ m
- :
(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)
:
- , /unit:
empty : A
- :
combine : (left: A, right: A) => A
3 :
- :
combine(empty, x) ≡ x
- :
combine(x, empty) ≡ x
- :
combine(combine(x, y), z) ≡ combine(x, combine(y, z))
? — , , . , «Monoids, monoids, monoids». Scala, — .
— , Foldable/Traversable , - ; Applicative ( , ) ; Task/TaskEither/Future , . . , .