ADTによるモデル化
不正な状態を表現不可能にします
「ADTによるモデル化」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。
代数的データ型
代数的データ型(ADT)は、関数型のドメインモデリングの基盤です。積型(AND)と和型(OR)を組み合わせて、データを正確に表現します。
- 積型:複数のフィールドを持つレコード。
- 和型:複数のバリアントからの選択。
case class Point(x: Int, y: Int) // product type
object Main:
def main(args: Array[String]): Unit =
println(Point(1, 2))積型
積型は値をまとめたものです。Scalaではcase classが積型にあたり、インスタンスはすべてのフィールドを同時に保持します。
case class User(name: String, age: Int)
object Main:
def main(args: Array[String]): Unit =
val u = User("Ada", 36)
println(u.name)
println(u.age)和型
和型は、複数のバリアントのうち正確に1つである値です。Scala 3のenumはこれを直接表現します。
enum PaymentMethod:
case Cash
case Card(number: String)
case Crypto(wallet: String)
object Main:
def main(args: Array[String]): Unit =
val p: PaymentMethod = PaymentMethod.Card("1234")
println(p)不正な状態を表現不能にする
核心となる原則は、不正なデータをそもそも構築できないように型を設計することです。型システムが不正な状態を禁止すれば、その状態を実行時に検査する必要はありません。
enum Connection:
case Disconnected
case Connected(sessionId: String)
object Main:
def main(args: Array[String]): Unit =
// No way to have a sessionId while Disconnected
val c: Connection = Connection.Connected("abc")
println(c)悪い設計と良い設計
nullableなフィールドを持つ平坦なレコードは、不正な状態を招きます。それぞれのケースをバリアントとしてモデル化すれば、不正な状態をなくせます。
- 悪い例:
case class Conn(connected: Boolean, sessionId: String)では、connected=falseなのにsessionIdが設定された状態を許してしまいます。 - 良い例:和型によって、フィールドを正しい状態に結び付けます。
enum Door:
case Open
case Closed
case Locked(key: String)
object Main:
def main(args: Array[String]): Unit =
val d: Door = Door.Locked("k1")
println(d)積型と和型の組み合わせ
実際のモデルでは、和型の中に積型を入れたり、その逆を行ったりします。各バリアントは、それぞれ独自のフィールドの積型を持てます。
case class Address(city: String, zip: String)
enum Contact:
case Email(value: String)
case Postal(address: Address)
object Main:
def main(args: Array[String]): Unit =
val c: Contact = Contact.Postal(Address("Paris", "75001"))
println(c)ADTのパターンマッチング
ADTはパターンマッチングで処理します。型が閉じているため、バリアントの処理漏れがあるとコンパイラーが警告します。
enum Shape:
case Circle(r: Double)
case Rect(w: Double, h: Double)
object Main:
def area(s: Shape): Double = s match
case Shape.Circle(r) => 3.14159 * r * r
case Shape.Rect(w, h) => w * h
def main(args: Array[String]): Unit =
println(area(Shape.Rect(2, 3)))再帰的なADT
ADTは自分自身を参照できるため、木構造、リスト、式を自然にモデル化できます。
enum Expr:
case Num(value: Int)
case Add(left: Expr, right: Expr)
object Main:
def eval(e: Expr): Int = e match
case Expr.Num(v) => v
case Expr.Add(l, r) => eval(l) + eval(r)
def main(args: Array[String]): Unit =
val e = Expr.Add(Expr.Num(2), Expr.Num(3))
println(eval(e))Optionによるオプショナリティ
値が存在しない可能性のあるフィールドのモデル化には、nullではなくOptionを使います。型によって不在が明示され、処理も必須になります。
case class Profile(name: String, nickname: Option[String])
object Main:
def main(args: Array[String]): Unit =
val p = Profile("Grace", None)
println(p.nickname.getOrElse("(none)"))数量を正確にモデル化する
プリミティブ型への過度な依存をやめましょう。IntやStringをそのまま使うのではなく、意味のある数量をラップすることで、型が意図を示し、取り違えを防ぎます。
case class Quantity(value: Int)
case class Price(cents: Long)
case class LineItem(qty: Quantity, price: Price)
object Main:
def main(args: Array[String]): Unit =
val item = LineItem(Quantity(3), Price(500))
println(item)ADTが重要な理由
ADTによって、正しさと明確さを得られます。
- 不正な状態を構築できません。
- 網羅的なマッチングによって、処理漏れをコンパイル時に検出できます。
- データの形状自体がドメインを説明します。
enum OrderStatus:
case Pending
case Shipped(tracking: String)
case Delivered(at: Long)
object Main:
def main(args: Array[String]): Unit =
val s: OrderStatus = OrderStatus.Shipped("TRK1")
println(s)理解度チェック
ADTに基づくモデリングについての理解度を確認しましょう。
まとめ
ADTを使ったドメインモデリングについて学びました。
- 積型(
case class)はANDでフィールドを組み合わせます。 - 和型(
enum)はORによる選択肢を提供します。 - 両者を入れ子にして、複雑なドメインや再帰的な構造をモデル化します。
- nullの代わりに
Optionを使います。 - 不正な状態を表現できないように設計します。
enum Event:
case Created(id: Int)
case Deleted(id: Int, reason: String)
object Main:
def describe(e: Event): String = e match
case Event.Created(id) => s"created $id"
case Event.Deleted(id, reason) => s"deleted $id: $reason"
def main(args: Array[String]): Unit =
println(describe(Event.Deleted(1, "spam")))よくある質問
「ADTによるモデル化」レッスンは無料ですか?
はい。「ADTによるモデル化」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。
「ADTによるモデル化」で何を学びますか?
不正な状態を表現不可能にします ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「ADTによるモデル化」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?
はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- ADTによるモデル化
- Smart Constructors
- Newtype
- ドメインの合成