来世から頑張る!!

技術ブログを目指して

Scalaの型推論って難しい

ラムダ計算とかの例によくある チャーチ数Scalaで試してみる。

原点を表す Zero 、次を表す Succ を定義しておけば足し算とかできるってやつ。

まずは試し書き

import Conrtol.Applicative

zero f a = a
succ n f a = f $ n f a

one = succ zero
two = succ one

add a b = liftA2 (.) a b

toInt n = n (\a -> a + 1) 0
toAs n = n (\a -> 'a' : a) []

a = add (succ two) one

main = do
    putStrLn $ show $ toInt a // => 4
    pusStrLn $ show $ toAs a // => "aaaa"

わざと型は明記していない。haskellなどのきちんと型推論してくれる言語であればこれで動く。

もしくは動的型の言語でも。

def zero(f):
    def x(a):
        return a
    return x

def succ(n):
    def x(f):
        def y(a):
            return f(n(f)(a))
        return y
    return x

one = succ(zero)
two = succ(one)

def add(n):
    def x(m):
        def y(f):
            def z(a):
                return n(f)(m(f)(a))
            return z
        return y
    return x

def to_int(n):
    return n(lambda x: x + 1)(0)

def to_as(n):
    return n(lambda x: "a" + x)("")

a = add(succ(two))(one)

to_int(a) # => 4
to_as(a) # => 'aaaa'

Scalaの場合

これがScalaだとまあ大変で。。。

def zero[A](f: A => A)(a: A) = a

この時点で上記の例ではなんでもよかった(使ってない) f の型を固定。
固定しない場合型指定が大変になる(後述)。

なお、戻り値の型は書くべきだと思うけれども、今回は型推論具合を知りたいから書かなくても動作する限り省略。

def succ[A](n: (A => A) => A => A)(f: A => A)(a: A) = f(n(f)(a))

def one[A]: (A => A) => A => A = succ(zero)
def two[A]: (A => A) => A => A = succ(one)

import scalaz._
import Scalaz._  // Applicativeの導入(必須ではないけど・・・)
def add[A](x: (A => A) => A => A)(y: (A => A) => A => A) = (x |@| y) { case (a, b) => a compose b }

def toInt(n: (Int => Int) => Int => Int) = n(a => a + 1)(0)
def toAs(n: (List[Char] => List[Char]) => List[Char] => List[Char]) = n(a => 'a' :: a)(List.empty)

// def a[A]: (A => A) => A => A = add(succ(two))(one) // => 型エラー!!
def a[A]= add[A](succ(two))(one)

println(toInt(a)) // => 4
println(toAs(a)) // => List(a, a, a, a)

型が必須な上に、 val が型引数を受け付けてくれないからギリギリまで def じゃないといけない。

できる限りHaskellでやった時の型に近づけると以下のような感じ?

def zero[F, A](f: F)(a: A) = a
def succ[A, B, C](n: (B => C) => A => B)(f: B => C)(a: A) = f(n(f)(a))

def one[A, B]: (A => B) => A => B = succ(zero)
def two[A]: (A => A) => A => A = succ(one)

def add[A, B, C, D](x: A => B => C)(y: A => D => B) = (x |@| y) { case (a, b) => a compose b }

def toInt(n: (Int => Int) => Int => Int) = n(a => a + 1)(0)
def toAs(n: (List[Char] => List[Char]) => List[Char] => List[Char]) = n(a => 'a' :: a)(List.empty)

// def a[A]: (A => A) => A => A = add(succ(two))(one) // => 型エラー!!
def a[A] = add[(A => A), A, A, A](succ(two))(one)

println(toInt(a)) // => 4
println(toAs(a)) // => List(a, a, a, a)

途中から A => A =>... みたいなのを書くことを考えれば、最初の方が綺麗な気がする。。。

最後に蛇足だけど引数の順序をScalaっぽくしてみる。

def zero[A, F](a: A)(f: F) = a
def succ[A, B, C](n: A => (B => C) => B)(a: A)(f: B => C) = f(n(a)(f))

def one[A, B]: A => (A => B) => B = succ(zero)
// def one[A, B] = succ[A, A, B](zero) _  // <- これでも通るのは通るみたい
def two[A]: A => (A => A) => A = succ(one)
// def two[A] = succ[A, A, A](one) _

def flip[A, B, C](f: A => B => C) = (b: B) => ((a: A) => f(a)(b)) // これどっかにないのかな???
// scalaz.Function2Optsならあるけど・・・ uncurriedみたいなのが必要だよなぁ。
def add[A, B, C, D](x: A => B => C)(y: C => B => D) = flip((flip(x) |@| flip(y)) { case (a, b) => a andThen b })

def toInt(n: Int => (Int => Int) => Int) = n(0)(a => a + 1)
def toAs(n: List[Char] => (List[Char] => List[Char]) => List[Char]) = n(List.empty[Char])(a => 'a' :: a)

// def a[A]: A => (A => A) => A = add(succ(two))(one) // => 型エラー!!
def a[A] = add[A, (A => A), A, A](succ(two))(one)

println(toInt(a)) // => 4
println(toAs(a)) // => List(a, a, a, a)

何度 A => と書いただろう・・・???