tkzwhr's notes

tkzwhrの技術、中国語などのメモです。

Scalaパターンマッチ集

定数パターン

expr match {
  case 1 => println("one")
  case _ => println("other")
}

変数パターン

expr match {
  case a => println(s"expr is $a")
  case _ => println("none")
}

型付きパターン

expr match {
  case a: MyClass => println("hit!")
  case _ => println("miss...")
}

型パラメータは消されるので、下記のようなことはできない。

expr match {
  case a: MyValue[Int] => println(s"my value is ${a.read}")
  case b: MyValue[String] => println(s"my value is '${a.read}'")
  _ => println("none")
}

コンストラクタパターン

expr match {
  case MyCaseAdd(l, r) => println(s"$l + $r = ${l + r}")
  case _ => println("none")
}

MyCaseAddはケースクラスである必要がある。

シールドクラス

基底クラスにシールドクラスを利用することで、全ケース網羅のチェックができる。

sealed abstract class Expr
case object Even extends Expr
case object Odd extends Expr

expr match {
  case Even => println("even")
}
// ==> warning: match is not exhaustive!
//     missing combination            Odd

シーケンス、タプルパターン

expr match {
  case List(a, b, _*) => println(s"$a, $b, and more...")
  case (a, b) => println(s"$a, $b")
  case _ => println("none")
}

_*で可変長シーケンスとのマッチが可能。

パターン束縛

expr match {
  case p @ MyPoint(x, y) => println(s"${p.invert}")
}

抽出子を使ったパターンマッチ

抽出子で柔軟なパターンマッチを行うを参照

パターンガード

ifでパターンマッチにさらに条件をつけられる。

expr match {
  case n if n % 2 == 0 => println(s"even: $n")
  case n => print(s"odd: $n")
}

ケースの変数展開

val (a, b) = expr

部分関数としてのケース

def twice(val: Option[Int]): Int = {
  case Some(n) => n * 2
  case _ => 0
}

抽出子で柔軟なパターンマッチを行う

unapply(a: T): Option[U]を実装したオブジェクトを抽出子と呼ぶ。

基本的にコンストラクタパターンにおけるパターンマッチはケースクラスに対して行うものであるが、抽出子を実装することで、ケースクラスでないオブジェクト(上記の例では数値)に対してもパターンマッチを適用することができる。

下記は1~100までの中で平方数を探すプログラムである。

import scala.math.sqrt

object Square {
  def apply(num: Int) = num * num
  def unapply(num: Int): Option[Int] = {
    if (sqrt(num) % 1 == 0) Some(sqrt(num).toInt) else None
  }
}

for (i <- 1 to 100) i match {
  case Square(x) => println(s"$i is a square number of $x.")
  case _ => println(s"$i is not a square number.")
}

返す値の数が可変長になる場合を考慮して、unapplySeq(a: T): Option[Seq[U]]というものも用意されている。

下記はタプル2から公約数を求めるプログラムである。

import scala.math._

object CommonDivisor {
  def unapplySeq(num: (Int, Int)): Option[Seq[Int]] = {
    val gcd = grand(num._1, num._2)
    if (gcd == 1) {
      Some(Seq(1))
    } else {
      val divisorsOfGcd = for {
        i <- 1 to ceil(sqrt(gcd)).toInt
        if gcd % i == 0
      } yield {
        if (i > 1 && i < sqrt(gcd)) Seq(i, gcd / i) else Seq(i)
      }
      Some(divisorsOfGcd.flatten.sorted :+ gcd)
    }
  }
  
  private def grand(a: Int, b: Int): Int = {
    val (higher, lower) = (max(a, b), min(a, b))
    if (lower == 0) higher else grand(lower, higher % lower)
  }
}

(120, 80) match {
  case n @ CommonDivisor(m @ _*) => println(s"All common divisors of $n are ${m.mkString(", ")}.")
}
// ⇒ "All common divisors of (120,80) are 1, 2, 4, 5, 8, 10, 20, 40."