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

Smart Constructors

検証済みの構築

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

検証済みの構築

スマートコンストラクターは、値を生成する前に入力を検証するファクトリです。保持するすべてのインスタンスが、その型の不変条件を満たすことを保証します。

  • 通常のコンストラクターは隠します。
  • 検証済みの生成だけを公開します。
class Age private (val value: Int)
object Age:
  def of(v: Int): Option[Age] =
    if v >= 0 then Some(new Age(v)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Age.of(30).map(_.value))

プライベートコンストラクター

プライマリコンストラクターにprivateを付けると、呼び出し側が検証を迂回できなくなります。コンパニオンオブジェクトが唯一のエントリーポイントになります。

class Percentage private (val value: Int)
object Percentage:
  def of(v: Int): Option[Percentage] =
    if v >= 0 && v <= 100 then Some(new Percentage(v)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Percentage.of(50).map(_.value))
    println(Percentage.of(150))

Optionを返す

Optionを返すことで、構築に失敗する可能性を示します。呼び出し側はNoneを処理する必要があるため、不正なデータが暗黙のうちに流れることはありません。

class NonEmpty private (val value: String)
object NonEmpty:
  def of(s: String): Option[NonEmpty] =
    if s.nonEmpty then Some(new NonEmpty(s)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(NonEmpty.of("").map(_.value))
    println(NonEmpty.of("hi").map(_.value))

エラーの詳細にはEitherを返す

検証に失敗した理由を説明したい場合は、Either[Error, T]を返します。Leftには説明的なメッセージを格納します。

class Username private (val value: String)
object Username:
  def of(s: String): Either[String, Username] =
    if s.isEmpty then Left("empty")
    else if s.length > 10 then Left("too long")
    else Right(new Username(s))

object Main:
  def main(args: Array[String]): Unit =
    println(Username.of(""))
    println(Username.of("ada").map(_.value))

case classによるスマートコンストラクター

コンストラクターをprivateにしたcase classも利用できます。ただし、自動生成されるapplyとcopyも制御する必要があるため、カスタムファクトリを定義し、コンストラクターをprivateにしておきます。

case class Email private (value: String)
object Email:
  def of(s: String): Option[Email] =
    if s.contains("@") then Some(Email(s)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Email.of("a@b.com"))
    println(Email.of("bad"))

検証済みの値を連結する

スマートコンストラクターはOptionまたはEitherを返すため、for内包表記で組み合わせて、より大きな検証済みオブジェクトを構築できます。

case class Name private (value: String)
object Name:
  def of(s: String): Option[Name] =
    if s.nonEmpty then Some(Name(s)) else None

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

object Main:
  def make(n: String): Option[Person] =
    for nm <- Name.of(n) yield Person(nm, 20)

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

入力の正規化

スマートコンストラクターでは、空白の除去や小文字化など、データを正規化することもできます。これにより、すべてのインスタンスが正規形を共有します。

case class Tag private (value: String)
object Tag:
  def of(s: String): Option[Tag] =
    val clean = s.trim.toLowerCase
    if clean.nonEmpty then Some(Tag(clean)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Tag.of("  Scala  "))

不変条件は常に維持される

値がスマートコンストラクターを通過すると、その不変条件は値の存続期間全体にわたって保証されます。後続のコードは再検証せずに、その値を信頼できます。

case class PositiveInt private (value: Int)
object PositiveInt:
  def of(n: Int): Option[PositiveInt] =
    if n > 0 then Some(PositiveInt(n)) else None

object Main:
  def doubleIt(p: PositiveInt): Int = p.value * 2 // always positive

  def main(args: Array[String]): Unit =
    PositiveInt.of(5).foreach(p => println(Main.doubleIt(p)))

Opaque Typeとの組み合わせ

コストゼロで検証済みの値を実現するには、スマートコンストラクターとopaque typeを組み合わせます。検証によって構築を制御しながら、ラッパーオブジェクトは割り当てられません。

object Domain:
  opaque type Score = Int
  def of(n: Int): Option[Score] =
    if n >= 0 && n <= 100 then Some(n) else None
  extension (s: Score) def value: Int = s

object Main:
  def main(args: Array[String]): Unit =
    println(Domain.of(88).map(_.value))

複数の検証ルール

複数のチェックを順番に適用します。最初に失敗したルールで処理を短絡し、明確なエラーを返します。

object Password:
  def of(s: String): Either[String, String] =
    if s.length < 8 then Left("too short")
    else if !s.exists(_.isDigit) then Left("need a digit")
    else Right(s)

object Main:
  def main(args: Array[String]): Unit =
    println(Password.of("abc"))
    println(Password.of("abcdef12"))

スマートコンストラクターを使う場面

通常の構築で不変条件を破る可能性がある型には、常にスマートコンストラクターを使います。

  • 範囲が制限された数値、空でない文字列、形式が決まったid。
  • 単純な成否にはOption、理由が必要な場合にはEitherを返します。
  • 通常のコンストラクターはprivateにします。
case class Port private (value: Int)
object Port:
  def of(n: Int): Option[Port] =
    if n >= 1 && n <= 65535 then Some(Port(n)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Port.of(8080))
    println(Port.of(70000))

理解度チェック

スマートコンストラクターについての理解度を確認しましょう。

まとめ

スマートコンストラクターについて学びました。

  • コンパニオンファクトリで入力を検証します。
  • 通常のコンストラクターをprivateにします。
  • 失敗処理を必須にするため、OptionまたはEitherを返します。
  • 必要に応じて入力を正規形に正規化します。
  • これにより、値の存続期間全体で不変条件が維持されます。
case class Even private (value: Int)
object Even:
  def of(n: Int): Option[Even] =
    if n % 2 == 0 then Some(Even(n)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Even.of(4))
    println(Even.of(5))

よくある質問

「Smart Constructors」レッスンは無料ですか?

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

「Smart Constructors」で何を学びますか?

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

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

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

「Smart Constructors」レッスンにはどのくらい時間がかかりますか?

ほとんどの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に戻る