Step 1: 型パラメータは契約である
1-1. 自分だけの Box を作る
// step1a.scala
// A box that holds anything (no type parameter)
class AnyBox(val value: Any)
// A box with a type parameter
class Box[A](val value: A)
@main def step1(): Unit =
// AnyBox: easy to put things in
val anyBox = AnyBox(42)
// val n: Int = anyBox.value // Compile error! Any is not Int
val n: Int = anyBox.value.asInstanceOf[Int] // Dangerous cast
// Box[A]: the type is preserved
val intBox = Box(42) // Inferred as Box[Int]
val m: Int = intBox.value // Comes out as Int — no cast needed
// Wrong type? Compile error.
// val s: String = intBox.value // error: Found Int, Required String
println(s"AnyBox: $n, Box[Int]: $m")
試してみよう:
> scala-cli run step1a.scala
次に val s: String = intBox.value のコメントを外してみてほしい。
ポイント: Box[A] の A は中に入れたものを「覚えている」のではない。
コンパイラは構築時に A = Int と確定する。これが契約だ。
値を取り出すとき、それは Int であることが保証される。
1-2. 関数の型パラメータ ― 契約は伝播する
// step1b.scala
def first[A](xs: List[A]): A = xs.head
@main def step1b(): Unit =
val names = List("Scala", "Rust", "Go")
val top: String = first(names) // Compiler resolves A = String
println(top)
val nums = List(1, 2, 3)
val one: Int = first(nums) // Compiler resolves A = Int
println(one)
// What about this?
val mixed = List(1, "two", 3.0)
val what = first(mixed) // What is A here?
println(what.getClass)
型を確認してみよう:
scala-cli run step1b.scala
# 推論された型を見る(コンパイルキャッシュを回避するため先にクリーン):
scala-cli clean step1b.scala && scala-cli compile step1b.scala -O -Xprint:typer 2>&1 | grep "mixed"
mixed は List[Int | String | Double] と推論される ― これはユニオン型で、Scala 3 の機能だ。
List[Int | String | Double] とは一度も書いていないことに注目してほしい ― コンパイラが推論したのだ。
本書を通じて、コンパイラが文脈から型パラメータを導き出す能力に頼る。
型推論が内部的にどう動くかはそれ自体が一つのテーマだが、ここでは単にそれが機能すると信頼し、
型が何を意味するかに集中する。