1. 概要

このチュートリアルでは、Higher-Kinded Types(HKT)について説明します。 まず、一般的な高種類のタイプについて説明します。 次に、Scalaでより種類の多い型を実装する方法を見ていきます。 最後に、より種類の多いタイプのいくつかのユースケースを見ていきます。

2. 高等種とは?

より種類の多いタイプは、あるタイプを抽象化し、次に別のタイプを抽象化するタイプです。 これは、型構築子をとるエンティティを一般的に抽象化する方法です。 さまざまなオブジェクトを処理できるモジュールを作成できます。 つまり、型コンストラクターを持つ型であるとも言えます。

より種類の多いタイプはScala2.5で導入されました。

3. Scalaでのより高種類のタイプの実装

Scala 2.5+は、より種類の多いタイプをファーストクラスでサポートしています。

これがどのように行われるかを確認するために、リストオプションなどのいくつかのコンテナタイプで利用できる単純なコレクションインターフェイスを定義します。 ]、およびアレイ。 特定のタイプに制限されることなく、コレクションをインスタンス化できるようになります。

trait Collection[T[_]] {
  def wrap[A](a: A): T[A]
  def first[B](b: T[B]): B
}

タイプTをパラメーターとして受け取るパラメーター化されたインターフェースであるCollectionを定義しました。 次に、Tは別のタイプをパラメーターとして受け取ります。 したがって、T [_]はタイプ_(何でも)のタイプTを意味します。  特性を次のように定義することもできます。

trait Collection[T[Z]] {
  ///
}

それでも同じ結果が得られます。 ただし、_を使用すると、Tが任意のタイプを受け入れることが明らかになります。

次に、 CollectionListで実装してみましょう。例:

var collection = new Collection[List] { 
  override def wrap[A](a: A): List[A] = List(a) 
  override def first[B](b: List[B]): B = b.head 
} 
assertEquals(collection.wrap("Some values"), List("Some values")) 
assertEquals(collection.first(List("Some values")), "Some values")

同様に、シーケンスのコレクションを作成できます。

var seqCollection = new Collection[Seq] {
  override def wrap[A](a: A): Seq[A] = Seq(a)
  override def first[B](b: Seq[B]): B = b.head
}
assertEquals(seqCollection.wrap("Some values"), Seq("Some values"))
assertEquals(seqCollection.first(Seq("Some values")), "Some values")

種類の多いタイプでは、どのコンテナタイプでも機能するCollectionを作成しました。

4. 高種類のタイプのユースケース

種類の多いタイプとは何かを見てきました。 私たちは主に抽象化の目的でより種類の多いタイプを使用します

いくつかのユースケースを見てみましょう。

4.1. ライブラリの設計と実装

種類の多いタイプのユースケースのほとんどは、ライブラリの設計と実装に見られます。 コードの重複を減らしながら、公開されたインターフェイスをより詳細に制御できるようにします。 最も人気のあるScalaプロジェクトの1つであるScalazは、関数型プログラミング用のコアScalaライブラリを拡張するために、より種類の多いタイプを使用します。

4.2. 多形コンテナ

より種類の多いタイプのユースケースの1つは、ポリモーフィックコンテナの作成です。 より種類の多いタイプは、あらゆるタイプのアイテムを保持できるコンテナーを作成する場合に役立ちます。 特定のコンテンツタイプごとに異なるタイプは必要ありません。 すでに見たように、コレクション(前の例では)はさまざまなエンティティタイプを許可します。

4.3. データパイプラインの構築

データエンジニアリングには、さまざまなデータの読み取り、変換、および書き込みが含まれます。 データの種類と量が増えると、関連するプロセスも増えます。

データの抽出、変換、読み込み(ETL)のパイプラインを設計する場合は、さまざまな種類のデータセットで機能するフレームワークが必要になる可能性があります。 特定のデータベースにデータを変換して書き込むことができるモジュールBatchRunの簡単な例を見てみましょう。

trait BatchRun[M[_]] {
  def write[A](item: A, db: M[A]): M[A] = transform(item, db)
  def transform[A](item: A, db: M[A]): M[A]
}

このようにして、あらゆるタイプのデータセットを処理できます。

val listDb: List[String] = List("data 1", "data 2")
var listBatchRun = new BatchRun[List] {
  def transform[A](item: A, db: List[A]): List[A] = db ::: item :: Nil
}
val savedList = listBatchRun.write("data 3", listDb)
assertEquals(savedList, List("data 1", "data 2", "data 3"))

val seqDb: Seq[Int] = Seq(1, 2)
val seqBatchRun = new BatchRun[Seq] {
  def transform[A](item: A, db: Seq[A]): Seq[A] = db :+ item
}
val savedSeq = seqBatchRun.write(3, seqDb)
shouldEqual(savedSeq, Seq(1, 2, 3))

実際には、このモジュールは多くのコードになる可能性があるため、考えられるすべてのタイプのデータセットをキャプチャするためだけにコードを繰り返すと、エネルギー、時間、およびスペースが無駄になります。

5. 結論

このチュートリアルでは、より種類の多いタイプについて説明しました。 まず、より種類の多いタイプを定義して説明しました。 次に、それらがScalaでどのように実装されているかをさらに確認しました。 最後に、より種類の多いタイプのいくつかのユースケースを見ました。 いつものように、記事の完全なソースコードは、GitHubから入手できます。