Scala入門・ゼロと後者関数

動機

自然数ってゼロとサクセサーだよね。

概要

という自然数の作り方がある。

Scalaの1つの特徴は、コンストラクタ以外のパターンマッチが書けること。
一般に関数型言語でパターンマッチができるのは、コンストラクタが単射な関数で、
逆関数があるから。
Scalaでは、自分で逆関数らしきものを書けば(元の関数がなくても)パターンマッチができるようになる。
これを利用して、Int(の部分集合)がゼロとサクセサーに見えるようにする。

実装

まず、ゼロを表現するオブジェクトZeroを定義する。

  • 0 : Intを返すapplyを定義する。
  • 0 : Intが来たらtrueを返す関数unapplyを定義する。

(パターンマッチのためには必ずしもapplyはいらない)

object Zero {
  def main(args : Array[String]) : Unit = {
    //this (singleton) object
    println(Zero)
    
    //this.apply
    println(Zero())
    
    //this.unapply
    println(unapply(0))
    println(unapply(1))
  }
  
  def apply() = 0
  def unapply(n : Int) : Boolean = n == 0
}

ZeroとZero()の違いが紛らわしい…。

次に、サクセサーを表現するオブジェクトSuccを定義する。

  • 0からMath.MAX_INT-1までのIntが来たら1を足して返す関数applyを定義する。
  • 1以上のIntが来たら1を引いてSomeに入れて返す関数unapplyを定義する。

Intはオーバーフローするので、MAX_INTのサクセサーは考えない。

object Succ {
  def main(args : Array[String]) : Unit = {
    println(Succ(2))
    println(unapply(3))
    //Exception
//    println(apply(Math.MAX_INT))
  }
  
  def apply(n : Int) : Int = {
    if(n >= 0 && n < Math.MAX_INT) (n + 1) else { throw new RuntimeException }    
  }
  
  def unapply(n : Int) : Option[Int] = {
    if(n <= 0) None else Some (n - 1)
  }
}

すると、ゼロとサクセサーでパターンマッチが書けるようになる。
これで階乗関数を書いてみたのが、下のFactorial。
applyの中でマイナスを書かなくてよいのがよいと思う。

object Factorial {
  def main(args : Array[String]) : Unit = {
    for(i <- 0 to 10){
      println(i + "! = " + Factorial(i))
    }
    for(i <- 0 to 100){
      i match {
        case Factorial(n) => println(i + " = " + n + "!")
        case _ =>
      }
    }
  }
  
  def apply(n : Int) : Int = {
    n match {
      case Succ(n2) => n * apply(n2)
      case Zero() => 1
    }
  }
  
  def unapply(n : Int) : Option[Int] = {
    var m = n
    var i = 1
    while(n > 0) {
      if (m % i != 0) return None
      m = m / i
      if (m == 1) return Some(i)
      i = i + 1
    }
    None
  }
}

ついでにFactorial.unapplyも書いてみた。
1以上の階乗は単射な関数なので、
逆関数を書くことで、階乗にマッチするようなパターンを書くことができる。
なお、unapply(apply(0)) = Some 1になるのがあまりよろしくない。

実行結果

0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
1 = 1!
2 = 2!
6 = 3!
24 = 4!