Previous articles in the series:
In the previous article, I described how you can emulate higher-order genus polymorphism in TypeScript. Let's now take a look at what this gives the functional programmer, and we'll start with the type class pattern.
The concept of a typeclass itself came from Haskell and was first proposed by Philip Wadler and Stephen Blott in 1988 to implement ad hoc polymorphism. A type class defines a set of typed functions and constants that must exist for each type that belongs to a given class. It sounds complicated at first, but it's actually quite a simple and elegant design.
What is a type class
, , . - TypeScript JavaScript , ( this
). , , GHC Core Language, .
Consider, as an example, one of the simplest type classes - Show
, - that defines a casting operation. It is defined in the module fp-ts/lib/Show
:
interface Show<A> {
readonly show: (a: A) => string;
}
This definition reads like this: a type A
belongs to a class Show
if A
a function is defined forshow : (a: A) => string
.
The type class is implemented as follows:
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 , . . , .