0Pricing
Scala for Backend Engineering & Functional Programming · レッスン

ドメインの合成

より大きなモデルを構築します

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

より大きなモデルを構築する

実際のドメインは、より小さな部品から組み立てられます。ドメインの合成とは、適切にモデル化された ADT、newtype、検証済みの値を組み合わせ、より大きく、それでも正しさを保つ構造を作ることです。

  • 小さく焦点を絞った型を、より大きな型へ合成できます。
  • 各部品は、それぞれの不変条件を維持します。
case class Money(cents: Long)
case class Product(name: String, price: Money)

object Main:
  def main(args: Array[String]): Unit =
    println(Product("Book", Money(1999)))

プロダクトのネスト

case class をネストしてプロダクト型を合成します。各レベルで構造を追加しながら、読みやすさも保てます。

case class Address(city: String, zip: String)
case class Customer(name: String, address: Address)

object Main:
  def main(args: Array[String]): Unit =
    val c = Customer("Ada", Address("London", "E1"))
    println(c.address.city)

コレクションとの合成

List、Map、Setを使って、ドメイン内の一対多の関係をモデル化します。

case class Item(name: String, qty: Int)
case class Order(id: Int, items: List[Item])

object Main:
  def main(args: Array[String]): Unit =
    val order = Order(1, List(Item("pen", 2), Item("ink", 1)))
    println(order.items.map(_.qty).sum)

サム型の合成

より大きな選択肢は、より小さな選択肢を組み合わせて作れます。enum のバリアント自体に別の ADT を含めることで、判断を幾層にも重ねられます。

enum Payment:
  case Card(last4: String)
  case Cash

enum Checkout:
  case Completed(payment: Payment)
  case Abandoned

object Main:
  def main(args: Array[String]): Unit =
    val c: Checkout = Checkout.Completed(Payment.Card("1234"))
    println(c)

ドメインを合成する関数

ドメイン操作は、入力型から出力型への純粋関数です。それらを合成することでワークフローを構築できます。

case class Cart(total: Long)
case class Receipt(total: Long, tax: Long)

object Main:
  def checkout(c: Cart): Receipt =
    Receipt(c.total, (c.total * 0.2).toLong)

  def main(args: Array[String]): Unit =
    println(checkout(Cart(1000)))

検証の合成

各フィールドをスマートコンストラクターで構築する場合は、for 内包表記で合成します。すべての部分が検証に成功した場合にのみ、オブジェクト全体が作成されます。

object V:
  def name(s: String): Option[String] = if s.nonEmpty then Some(s) else None
  def age(n: Int): Option[Int] = if n >= 0 then Some(n) else None

case class Person(name: String, age: Int)

object Main:
  def make(n: String, a: Int): Option[Person] =
    for nm <- V.name(n); ag <- V.age(a) yield Person(nm, ag)

  def main(args: Array[String]): Unit =
    println(Main.make("Bob", 30))
    println(Main.make("", 30))

複合データの畳み込み

fold、または map と sum を組み合わせて、合成された構造を集約します。データの形が計算方法を導きます。

case class Line(price: Long, qty: Int)
case class Invoice(lines: List[Line])

object Main:
  def total(inv: Invoice): Long =
    inv.lines.foldLeft(0L)((acc, l) => acc + l.price * l.qty)

  def main(args: Array[String]): Unit =
    val inv = Invoice(List(Line(100, 2), Line(50, 4)))
    println(total(inv))

不変モデルの更新

copyを使って変更を合成します。copyは、指定したフィールドを置き換えた新しい値を返します。元の値は変更されません。

case class Account(id: Int, balance: Long)

object Main:
  def deposit(a: Account, amt: Long): Account =
    a.copy(balance = a.balance + amt)

  def main(args: Array[String]): Unit =
    val a = Account(1, 100)
    println(deposit(a, 50))

ステートマシンのモデル化

サム型と遷移関数を合成して、合法な遷移だけが存在するステートマシンをモデル化します。

enum Light:
  case Red, Green, Yellow

object Main:
  def next(l: Light): Light = l match
    case Light.Red    => Light.Green
    case Light.Green  => Light.Yellow
    case Light.Yellow => Light.Red

  def main(args: Array[String]): Unit =
    println(next(Light.Red))
    println(next(Light.Green))

ドメインと操作のレイヤー化

データ型は純粋に保ち、振る舞いは関数またはメソッドに配置します。合成されたモデルと一連の変換を組み合わせると、小規模なドメインライブラリになります。

case class Temp(celsius: Double):
  def toF: Double = celsius * 9 / 5 + 32

object Main:
  def main(args: Array[String]): Unit =
    val t = Temp(25.0)
    println(t.toF)

全体を組み合わせる

完全なモデルでは、newtype、プロダクト、サム、コレクション、検証済みの構築を合成します。その結果、誤って使いにくく、理解しやすいモデルになります。

  • まず小さな型を構築します。
  • それらを集約へ合成します。
  • 操作を純粋関数として表現します。
case class Sku(value: String)
case class Line(sku: Sku, qty: Int)
case class Basket(lines: List[Line]):
  def count: Int = lines.map(_.qty).sum

object Main:
  def main(args: Array[String]): Unit =
    val b = Basket(List(Line(Sku("A"), 2), Line(Sku("B"), 3)))
    println(b.count)

理解度チェック

ドメインの合成についての理解度を確認しましょう。

まとめ

ドメインを合成する方法を学びました。

  • プロダクトとサムをネストして、豊かな構造をモデル化します。
  • コレクションを一対多の関係に使用します。
  • for 内包表記で検証を合成します。
  • copyで不変のまま更新します。
  • データを純粋に保ち、操作を関数として表現します。
case class User(id: Int, name: String)
case class Post(author: User, text: String)

object Main:
  def main(args: Array[String]): Unit =
    val p = Post(User(1, "Ada"), "hello")
    println(p.copy(text = "hi"))

よくある質問

「ドメインの合成」レッスンは無料ですか?

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

「ドメインの合成」で何を学びますか?

より大きなモデルを構築します ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?

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

「ドメインの合成」レッスンにはどのくらい時間がかかりますか?

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

このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?

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

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

  1. ADTによるモデル化
  2. Smart Constructors
  3. Newtype
  4. ドメインの合成
← Scala for Backend Engineering & Functional Programmingに戻る