Frühere Artikel in der Reihe:
Im vorherigen Artikel habe ich beschrieben, wie Sie Gattungspolymorphismus höherer Ordnung in TypeScript emulieren können. Lassen Sie uns nun einen Blick darauf werfen, welche Möglichkeiten dies dem funktionalen Programmierer bietet, und wir beginnen mit dem Muster der Typklasse.
Das Konzept einer Typklasse stammt von Haskell und wurde erstmals 1988 von Philip Wadler und Stephen Blott vorgeschlagen, um einen Ad-hoc-Polymorphismus zu implementieren. Eine Typklasse definiert eine Reihe typisierter Funktionen und Konstanten, die für jeden Typ vorhanden sein müssen, der zu einer bestimmten Klasse gehört. Es klingt zunächst kompliziert, ist aber eigentlich ein recht einfaches und elegantes Design.
Was ist eine Typklasse?
, , . - TypeScript JavaScript , ( this
). , , GHC Core Language, .
Betrachten Sie als Beispiel eine der einfachsten Typklassen - Show
, -, die eine Umwandlung in eine Zeichenfolge definiert. Es ist im Modul definiert fp-ts/lib/Show
:
interface Show<A> {
readonly show: (a: A) => string;
}
Diese Definition lautet wie folgt : Ein Typ A
gehört zu einer Klasse, Show
wenn A
eine Funktion für definiert istshow : (a: A) => string
.
Die Typklasse wird wie folgt implementiert:
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 , . . , .