OneDay

40歳からプログラマとして生活しています。

高階関数分かってきた気がする、Ruby関数定義するときにcurry使えそう - Scala, Ruby

EXERCISE 2.5 (34ページ)
2つの関数を合成する高階関数を実装せよ

def compose[A, B, C](f: B => C, g: A => B): A => C

高階関数とは、他の関数を引数として受け取る関数のこと。
テキストでは、メソッドに関数を定義して、それを別のメソッドで呼び出す例がある。

object MyModule {
    def factorial2(n: Int): Int ={
        var acc = 1
        var i = n
        while (i > 0) { acc *= i; i -= 1}
        acc
    }

    def formatResult(name: String, n: Int, f: Int => Int) = {
        val msg ="The %s of %d is %d."
        msg.format(name, n, f(n))
    }

    def main(args: Array[String]): Unit = {
         println(formatResult("factorial", 7, factorial2))
    }
}

このコードを実行すると下記が返る。

res3: String = The factorial of 7 is 5040.

MyModuleのformatResultメソッドの引数の中にf: Int => Intという関数が入っている。ある数字が関数によって変化した値が返る。この例では関数にfactorial2を指定している。factorial2は入力する数字までを乗算する関数。5を入れると 5 * 5 * 3 * 2 * 1の値を返す。つまり、formatResultメソッド(関数)の引数としてfactorial2(関数)を受け取っている。こういうのを高階関数という。



最初のEXERCISE 2.5に戻る。

def compose[A, B, C](f: B => C, g: A => B): A => C

compose関数の引数にはfとgの関数が2つある。
この関数の返り値もまた関数であり、A型の値を入れるとC型の値を返す関数となる。

実装していく。右辺のAは、A型の値aで、Cはf:B=>Cから取得。

def compose[A, B, C](f: B => C, g: A => B): A => C =
    a: A => f(b:B)

次に右辺のBはg:A => Bから取得。

def compose[A, B, C](f: B => C, g: A => B): A => C =
    a: A => f(g(a:A))

型は推測可能なので省略。

def compose[A, B, C](f: B => C, g: A => B): A => C =
    a => f(g(a))

関数の中に関数があるという実装になる。REPLを用いて、使い方を確認してみる。

scala> val calc1 = (x:Int) => x * x
calc1: Int => Int = <function1>

scala> val calc2= (x:Int) => "Result: " +  x
calc2: Int => String = <function1>

scala> val result = compose(calc2, calc1)
result: Int => String = <function1>

scala> result(10)
res8: String = Result: 100

compose関数の引数の順序を変えてみると怒られる。関数の順序は意識するようにしよう。

scala> val result = compose(calc1, calc2)
<console>:13: error: type mismatch;
 found   : Int => Int
 required: String => Int
       val result = compose(calc1, calc2)


ここでやったcomposeというメソッドScalaの標準ライブラリで実装されている。andThenというメソッドは関数の順序を意識したメソッドネーミングのように見える。「この関数の次は、この関数」ということを考えるとandThenを使ったほうが理解しやすいかもしれない。

scala> val compose_1 = calc2 compose calc1
compose_1: Int => String = <function1>

scala> compose_1(10)
res9: String = Result: 100


scala> val compose_2 = calc1 andThen calc2
compose_2: Int => String = <function1>

scala> compose_2(10)
res11: String = Result: 100


これをRubyでもやってみる。

[1] pry(main)> compose_setting =->(f,g,a){f.(g.(a))}
=> #<Proc:0x007fb32d8cbf80@(pry):1 (lambda)>
[2] pry(main)> compose_curry = compose_setting.curry
=> #<Proc:0x007fb32d8f2ea0 (lambda)>
[3] pry(main)> calc1 =->(x){ x * x }
=> #<Proc:0x007fb32d919c30@(pry):3 (lambda)>
[4] pry(main)> calc2 =->(x){ "Result: " + x.to_s }
=> #<Proc:0x007fb32cd282c8@(pry):4 (lambda)>
[5] pry(main)> compose = compose_curry.(calc2).(calc1)
=> #<Proc:0x007fb32f66b2e8 (lambda)>
[6] pry(main)> compose.(10)
=> "Result: 100"

Scalaだとこの関数合成のときに引数を気にしなくても 関数a と 関数bを合成して関数cを作ることができる。関数cに適切な引数をもって実行すると適切な値が返る。一方Rubyではlambda式を用いたが、compose関数を実装するにあたって、予めcompose関数の引数をセットしなければならない。pry[1]の行がそれを表している。fとgが関数でaが引数。

pry[1] : ブロックの引数を(関数1, 関数2, 合成した関数の引数)とし、->(f,g,a){f.(g.(a))}としている。このf.(g.(a))という書き方はprocオブジェクトを呼び出すcallメソッドシンタックスシュガーなので、もともとはf.call(g.call(a))である。[]を用いてf[g[a]]でもよい。

pry[2]: ここでいきなりcurry化し、それをcompose_curryというオブジェクトにしている。curry化すれば最初から任意の引数まで内包したオブジェクトにすることができる。curryについては、昨日記したエントリーをご参照下さい。curryが良くわからなかったので、ひたすら引用したら少し見えてきた -Scala, Ruby - ノグレブ管理人のblog何が嬉しいかというと合成した関数の引数だけを引数とした関数にすることができる。

pry[3]: pry[4]: compose関数に取り込みたい関数2つ

pry[5]: compose = compose_curry.(calc2).(calc1) でcompose_settingの最初2つの関数まで内包したcompose関数の出来上がり。

pry[6]: compose.(10) でcompose関数に10を入れているようになる。 curryは引数数を見ていて、curry元の引数数分callされるとcurry元のブロックが評価される。ここではcurry元 compose_settingの引数は 3つ。 composeは引数2つcurry化し、残り1つ。 compose.(10)で最後の1つが呼び出され、引数数が揃うのでcompose_setting関数が呼び出される。