りんごとバナナとエンジニア

エンジニア修行の記録

【Scala】パターンマッチングでの "unbound placeholder parameter" エラー

Scala関数型デザイン&プログラミング」を進めている。今は3章でデータ型を定義しているところなのだが、パターンマッチングを書いたときに凡ミスでエラーを起こしたのでメモ。

sealed trait List[+A]

case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]

object List {
    def apply[A](as: A*): List[A] = {
        if (as.isEmpty) Nil
        else Cons(as.head, apply(as.tail: _*))
    }
}

このようなList型の定義がある時、このコンパニオンオブジェクトの中に、任意の個数の要素をListの先頭から消す drop メソッドを追加したい。そこで以下のように書いたところエラーが発生した。

def drop[A](as: List[A], n: Int): List[A] = (as, n) match {
    case (Nil, _) => Nil // n個消す前に要素が全部消えたらNil
    case (_, 0) => _ // n個消し終わったら残った要素のListを返す
    case (Cons(_, xs), i) => drop(xs, i-1) // 先頭を一つ消したリストを使い再帰を行う
}

// Main.scala
val x = List(1, 2, 3, 4, 5)
println(List.drop(x, 4)) // unbound placeholder parameter case (_, 0) => _

エラーの原因は、2番目のcaseで返される値が _ になっていること。ついcase(_, 0) で指定されている _ と同じ変数である感覚で書いてしまったが、 _Javaのswitch文で言う default に相当するものであり、特定の値を格納する変数ではない。 そのため、返される値の _ が何にも束縛されておらず、 unbound placeholder parameter のエラーとなった。

正しく書くには、条件部分で _ ではなく変数を使い、それをそのまま返す値として渡すようにすれば良い。

def drop[A](as: List[A], n: Int): List[A] = (as, n) match {
    case (Nil, _) => Nil
    case (l, 0) => l
    case (Cons(_, xs), i) => drop(xs, i-1)
}

ただし、これだと2番目のcaseは受け取った as 引数をそのまま返しているだけなので冗長になる。このようなcaseは、if文としてパターンマッチングの外に出してしまった方が良い。

def drop[A](as: List[A], n: Int): List[A] = {
  if (n <= 0) as
  else as match {
    case Nil => Nil
    case Cons(_, xs) => drop(xs, n-1)
  }
}