Articles précédents de la série:
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
, , . - 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 :
- , , , .
- , , «» . , /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 , . . , .