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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- ADTによるモデル化
- Smart Constructors
- Newtype
- ドメインの合成