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

2019.7.20 12:00

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."

scala

Copyright 2020 tkzwhr's tech notes. All Rights Reserved. Built with Gatsby.