型クラスと暗黙の仕組み
アドホック多相性のための型クラスの使い方と、強力な抽象化を実現するScalaの暗黙の仕組みを学びます。
「型クラスと暗黙の仕組み」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン3/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全3レッスンが含まれています。
アドホック多相性とは
Scalaにおける多相性とは、異なる型で動作するコードを記述することです。これまでに、継承を使ったサブタイピングによる多相性を見てきました。これは、基底クラスとそのすべてのサブクラスでメソッドを動作させるものです。
アドホック多相性はそれとは異なります。継承関係のない型であっても、渡された具体的な型に応じて1つの関数を異なる動作にできます。ここで型クラスが活躍します。
型クラスとは
Type Classは、既存の型を変更したり継承を使ったりせずに、新しい振る舞いを追加する方法を提供するデザインパターンです。
- 特定の振る舞いに対する契約(trait)を定義します。
- 異なる型に対してこの契約を実装する「インスタンス」(オブジェクト)を提供します。
- Scalaのimplicitメカニズムを使って、適切な振る舞いを自動的にスコープへ導入します。
Type Classのtraitを定義する
まず、必要な振る舞いの概要を定義するtraitを作成します。このtraitは型パラメーターAを受け取ります。Aは、振る舞いを適用する対象の型を表します。
ここでは、任意の型を人間が読みやすい文字列に変換する方法を知っているPrintable Type Classを作成します。
package com.coddykit
trait Printable[A] {
def format(value: A): String
}Type Classのインスタンスを作成する
次に、特定の型をPrintableにする方法をScalaに伝える必要があります。そのために、型ごとにPrintable traitを拡張するimplicit object(または値)を作成します。
これらをType Classのインスタンスと呼びます。
package com.coddykit
trait Printable[A] {
def format(value: A): String
}
object PrintableInstances {
implicit object StringPrintable extends Printable[String] {
def format(value: String): String = s"'${value}'"
}
implicit object IntPrintable extends Printable[Int] {
def format(value: Int): String = s"${value}i"
}
}Implicitを使ってType Classを利用する
Type Classを利用するには、Type Class型のimplicitパラメーターを受け取る関数を定義します。この関数が呼び出されると、Scalaのコンパイラーは利用可能なPrintable[A]のimplicitインスタンスを自動的に検索します。
見つかると、それを関数に注入します。
package com.coddykit
trait Printable[A] {
def format(value: A): String
}
object PrintableInstances {
implicit object StringPrintable extends Printable[String] {
def format(value: String): String = s"'${value}'"
}
implicit object IntPrintable extends Printable[Int] {
def format(value: Int): String = s"${value}i"
}
}
object PrintableSyntax {
def print[A](value: A)(implicit p: Printable[A]): Unit = {
println(p.format(value))
}
}
object Main {
import PrintableInstances._
import PrintableSyntax._
def main(args: Array[String]): Unit = {
print("hello")
print(123)
}
}Context Bounds:より簡潔な構文
Scalaには、implicitパラメーター用のcontext boundという短縮記法があります。(implicit p: Printable[A])の代わりに、[A: Printable]と記述できます。
implicitパラメーターが複数ある場合などに、コードをより簡潔にできます。
package com.coddykit
trait Printable[A] {
def format(value: A): String
}
object PrintableInstances {
implicit object StringPrintable extends Printable[String] {
def format(value: String): String = s"'${value}'"
}
implicit object IntPrintable extends Printable[Int] {
def format(value: Int): String = s"${value}i"
}
}
object PrintableSyntax {
// Using context bound: [A: Printable] is shorthand for (implicit p: Printable[A])
def print[A: Printable](value: A): Unit = {
// To access the implicit instance, use implicitly[Printable[A]]
val p = implicitly[Printable[A]]
println(p.format(value))
}
}
object Main {
import PrintableInstances._
import PrintableSyntax._
def main(args: Array[String]): Unit = {
print("world")
print(456)
}
}カスタム型への拡張
Type Classの強みは、どのような型にも新しい振る舞いを追加できることです。IntやStringのように自分が所有していない型にも、case classのような独自のカスタム型にも追加できます。
ここでは、Person case classをPrintableにしてみましょう。
package com.coddykit
trait Printable[A] {
def format(value: A): String
}
case class Person(name: String, age: Int)
object PrintableInstances {
implicit object StringPrintable extends Printable[String] {
def format(value: String): String = s"'${value}'"
}
implicit object IntPrintable extends Printable[Int] {
def format(value: Int): String = s"${value}i"
}
// New instance for our custom Person type
implicit object PersonPrintable extends Printable[Person] {
def format(person: Person): String =
s"Person(name: ${person.name}, age: ${person.age})"
}
}
object PrintableSyntax {
def print[A: Printable](value: A): Unit = {
val p = implicitly[Printable[A]]
println(p.format(value))
}
}
object Main {
import PrintableInstances._
import PrintableSyntax._
def main(args: Array[String]): Unit = {
val alice = Person("Alice", 30)
print(alice)
}
}Implicit:Type Classだけではない仕組み
Type Classは主な利用例の一つですが、Scalaのimplicitキーワードは、より広い範囲で使える仕組みです。次の用途に利用できます。
- Implicitパラメーター:Type Classで見たように、値を自動的に提供します。
- Implicit変換:ある型を別の型へ自動的に変換します(コードの流れを追いにくくする可能性があるため、注意して使用してください)。
- Implicit値:特定の型に対するデフォルト値を提供します。実行コンテキストなどでよく使われます。
重要なのは、コンパイラーがスコープ内から適切なimplicit定義を検索するという点です。
Type Classのメリット
Type Classには、次のようなメリットがあります。
- 拡張性:既存の型を変更せずに、新しい振る舞いを追加できます。
- 疎結合:型の定義と振る舞いを分離できます。
- アドホック多相:必要な振る舞いを提供する任意の型で関数を動作させられます。
- テスト容易性:振る舞いを分離してテストしやすくなります。
- 継承による負担がない:深い継承階層の複雑さを避けられます。
クイックチェック
このレッスンで扱ったPrintable Type Classとそのインスタンスについて考えてみましょう。DoublePrintableインスタンスが存在しないと仮定した場合、print(10.5)を呼び出すとどうなるでしょうか。
まとめと次のステップ
Type ClassとScalaの強力なimplicitシステムを習得しました。
- Type Classを使うと、継承せずに型の振る舞いを定義でき、アドホック多相を実現できます。
- Type Classは、trait(契約)とimplicit object/値(インスタンス)で構成されます。
- 関数はimplicitパラメーターとcontext boundを使って、これらのインスタンスを自動的に検索し、利用します。
Type ClassはScalaにおける関数型プログラミングの基盤であり、CatsやZIOなどのライブラリで、柔軟で堅牢なアプリケーションを構築するために幅広く使われています。理解を確かなものにするため、練習を続けてください。
よくある質問
「型クラスと暗黙の仕組み」レッスンは無料ですか?
はい。「型クラスと暗黙の仕組み」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全3レッスンが含まれています。
「型クラスと暗黙の仕組み」で何を学びますか?
アドホック多相性のための型クラスの使い方と、強力な抽象化を実現するScalaの暗黙の仕組みを学びます。 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/3です。
「型クラスと暗黙の仕組み」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?
はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- ジェネリクスと型パラメーター
- 変性:共変と反変
- 型クラスと暗黙の仕組み