0Pricing
TypeScript Academy · レッスン

軽量HKTパターン

Kind/URIのデファンクショナライゼーション技法を実装します。

「軽量HKTパターン」はCoddyKit上の無料TypeScript Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはTypeScript Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 TypeScript Academyコースには全4レッスンが含まれています。

デファンクショナライゼーション

このエンコーディングの背後にある仕組みには、デファンクショナライゼーションという名前があります。実際の高階型関数を使う代わりに、各関数をタグで表現し、単一の第一階の検索で解決します。URItoKindレジストリがその検索テーブルです。

type Kind<F extends URIS, A> = URItoKind<A>[F];
// One lookup replaces true type-function application

Kind型

Kind<F, A>は、このパターンを外部に見せるための型です。「AにFコンストラクターを適用した型」と読みます。レジストリのインデックス参照を、わかりやすい名前の背後に隠します。

type A = Kind<"Array", number>;  // number[]
type B = Kind<"Option", string>; // string | null

FをURISに制約する

Fは登録済みのURIでなければなりません。extends URISで制約することで、有効なタグだけを使用でき、タイプミスをコンパイルエラーにできます。

type Kind<F extends URIS, A> = URItoKind<A>[F];

// Kind<"Arrey", number> -> error, not a valid URI

汎用シグネチャの記述

これで、以前は記述できなかったFunctorのシグネチャを書けるようになります。すべてのF<A>をKind<F, A>に置き換えると、コンパイラーは受け入れてくれます。

interface Functor<F extends URIS> {
  map<A, B>(fa: Kind<F, A>, f: (a: A) => B): Kind<F, B>;
}

解決の実例

Fを具体的なURIに固定すると、Kindは実際のコンテナ型に解決されます。Functor<"Array">には、A[]からB[]へのmapがあります。

type ArrayFunctor = Functor<"Array">;
// map: <A, B>(fa: A[], f: (a: A) => B) => B[]

複数の型引数

Either<E, A>のように、2つの引数を取るコンストラクターもあります。fp-tsでは、URItoKind2とKind2<F, E, A>を使って、カインド* -> * -> *に対応するようパターンを拡張しています。レジストリを増やすことで、この考え方を拡張できます。

interface URItoKind2<E, A> {
  Either: { left: E } | { right: A };
}
type Kind2<F extends URIS2, E, A> = URItoKind2<E, A>[F];

fp-tsの規約

fp-tsは、このコース全体で採用する規約を確立しました。データ型ごとに1つのURI文字列を用意し、宣言マージで拡張するURItoKindレジストリと、解決に使うKindを用います。この規約がわかると、fp-tsやEffectの型シグネチャを読めるようになります。

// In fp-ts you will see exactly:
// declare module "fp-ts/HKT" { interface URItoKind<A> { ... } }

インスタンスレコード

抽象化の「インスタンス」とは、特定のURIに対してそれを実装する値です。インスタンスは具体的なmapを保持します。次のレッスンでArrayのインスタンスを記述します。ここではその形だけを確認します。

declare const arrayFunctor: Functor<"Array">;
const out = arrayFunctor.map([1, 2, 3], n => n * 2); // number[]

「軽量」と呼ばれる理由

軽量HKTパターンと呼ばれるのは、コンパイラーの変更も特別な構文も必要なく、インターフェースとインデックスアクセスだけで実現できるからです。URIやレジストリを用意する手間は少しかかりますが、コンテナに対する真の汎用コードが得られます。

type Kind<F extends URIS, A> = URItoKind<A>[F];
// No magic, just a typed lookup table

制限事項

このパターンは冗長で、ドメインではなくエンコーディングに関する情報がエラーメッセージに現れるため、内容がわかりにくいことがあります。また、すべてのコンテナを登録する必要もあります。多くのアプリでは、自分で構築するのではなく、すでにこの仕組みを採用しているライブラリを利用します。

// Trade-off: real abstraction power vs. encoding ceremony

全体を組み立てる

軽量HKTの完全な手順は、URIを定義し、URItoKindに登録し、Kind<F, A>を使って抽象化を定義し、URIごとにインスタンスを提供することです。これが、TypeScriptの汎用関数型ライブラリを支えるツールキットの全体です。

type Kind<F extends URIS, A> = URItoKind<A>[F];
interface Functor<F extends URIS> {
  map<A, B>(fa: Kind<F, A>, f: (a: A) => B): Kind<F, B>;
}

クイックチェック

軽量HKTパターンについての理解度を確認しましょう。

まとめ

軽量HKTパターンを組み立てました。

  • デファンクショナライゼーションによって、型関数の適用を検索に置き換えます。
  • Kind<F, A>はURIと引数から具体的な型を解決します。
  • 汎用シグネチャではF<A>の代わりにKindを使います。
  • Kind2を使えば2つの引数にも対応でき、これがfp-tsの規約です。

次は、実際のインスタンスを使った汎用mapです。

よくある質問

「軽量HKTパターン」レッスンは無料ですか?

はい。「軽量HKTパターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、TypeScript Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 TypeScript Academyコースには全4レッスンが含まれています。

「軽量HKTパターン」で何を学びますか?

Kind/URIのデファンクショナライゼーション技法を実装します。 ブラウザで直接実行するハンズオンコードでTypeScript Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

TypeScript Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのTypeScript Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「軽量HKTパターン」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このTypeScript Academyレッスンでコードを書いて実行できますか?

はい。すべてのTypeScript Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. TypeScriptにおけるHKTの問題
  2. 型コンストラクターの定義
  3. 軽量HKTパターン
  4. ジェネリックなFunctorとMapper
← TypeScript Academyに戻る