Scalaのカリー化に納得がいかなかった(しちゃんと試してなかった)ので、サンプルを作りながら試していく。基本的にはScalaByExampleをパクりながら ^^;
実験素材
さて、まずはsum。面倒だったのでtraitとして実装。一番基本的な形での定義。これはScalaByExampleにも書かれてている通り、1引数の関数fを渡すと、2引数の関数を生成して返す関数。返された関数の引数として「開始」と「終了」の数を指定して呼び出すと、その間の整数1つ1つに対して関数fを適用し、すべて足し合わせて返してくれるというもの。説明めんどくさい、サンプル間違えたかな…まぁいいや。
実験素材
さて、まずはsum。面倒だったのでtraitとして実装。一番基本的な形での定義。これはScalaByExampleにも書かれてている通り、1引数の関数fを渡すと、2引数の関数を生成して返す関数。返された関数の引数として「開始」と「終了」の数を指定して呼び出すと、その間の整数1つ1つに対して関数fを適用し、すべて足し合わせて返してくれるというもの。説明めんどくさい、サンプル間違えたかな…まぁいいや。
trait CurryingSample {お次は呼び出しコード。
def sum(f: Int => Int): (Int, Int) => Int = {
def sumF(a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sumF(a + 1, b)
sumF
}
}
object CurryingMain extends CurryingSample {このサンプルでは、各数に適用する関数は匿名関数 x => x。つまり、引数をそのまま返す関数。というわけで、ここで生成されている関数「simpleSum」は、指定された範囲の整数を単純に足し合わせて返す。実行してみると…
def main(args : Array[String]) : Unit = {
val simpleSum = sum(x => x)
println(simpleSum(0, 5))
}
}
15となる。オッケー。ここまでは、ScalaByExampleで書いてあることだし、わかっていた。問題は、カリー化の対象になる関数をどのくらい柔軟に記述できるか。じゃ、試していく。
短縮記法
まずは、ScalaByExampleに書いてあった短縮記法。traitに以下を追加。
気を取り直して、コンパイルがとおるようになおす。
def shortSum(f: Int => Int)(a: Int, b:Int) : Int = {呼び出し側を以下のように変更。
if (a > b) 0 else f(a) + shortSum(f)(a + 1, b)
}
object CurryingMain extends CurryingSample {これは、コンパイルエラーになる。メッセージはこんな感じのもの。
def main(args : Array[String]) : Unit = {
val simpleSum = shortSum(x => x)
println(simpleSum(0, 5))
}
}
missing arguments for method shortSum in trait CurryingSample;どうやら、この記法の場合は末尾に「_」をつけなくてはCurry化かどうか推測できないみたいだ。まぁ、もともとカリー化する場合には「_」をつけなければならず、場合によって省略できるということになっているのでいいのか?
follow this method with `_' if you want to treat it as a partially applied function
気を取り直して、コンパイルがとおるようになおす。
val simpleSum = shortSum(x => x)_コンパイルが通るようになり、実行すると以下のように出力される。
15オーケー。
高階関数を普通の関数として呼び出し
次は、shortSumを高階関数じゃなく普通の関数として呼び出せるかどうか。呼び出し側を以下のように変える。カリー化じゃなくて、値を全部渡す。
def main(args : Array[String]) : Unit = {コンパイルは通る。実行すると…
val simpleSum = shortSum(x => x)(1, 2)
println(simpleSum)
}
3正常に動いている。高階関数だからといって、高階関数専用になってしまうわけではないらしい。これはちょっと嬉しい。sumでも同様に問題なく動作する。
部分適用範囲の変更
次は、適用する引数を増やそうとしてみる。まずは今のままで、呼び出し側を変更する。
次は、適用する引数を増やそうとしてみる。まずは今のままで、呼び出し側を変更する。
val simpleSum = shortSum(x => x)(1)_1引数目を1で固定した関数を作ろうとした…が、当然のごとくコンパイルエラー。そりゃそうか。
wrong number of arguments for method shortSum: (Int,Int)Intうーん、エラー表記の意味がわからん…shortSumの型が(Int,Int)Intってどういうことだ?shortSumの型は(Int => Int) => Intみたいな型のはずだけど…それをこう書くの?よくわからん。とりあえず引数の数が違うらしい。範囲を部分適用できるように、shortSumを修正してみる。
def shortSum(f: Int => Int)(a: Int)(b:Int) : Int = {範囲を表すaとbのそれぞれを()で囲み、別々の引数となるようにしてみた。さて、これで部分適用は可能か。呼び出し側。
if (a > b) 0 else f(a) + shortSum(f)(a + 1)(b)
}
def main(args : Array[String]) : Unit = {おぉ、コンパイル通る。ちなみにこの場合も、「_」をつけないとコンパイルエラーになる。実行してみると…
val simpleSum = shortSum(x => x)(1)_
println(simpleSum(5))
}
15きた!
わかったこと
結局は、部分適用したい単位ごとに()で囲わないといけないということか。やっぱりHaskellみたいにフリーダムじゃないのね。とはいえ、思っていたよりは使えそうかも。安心した。部分適用するかもしれない関数の引数は、なんでもかんでもバラバラに()で切り刻んでおけばOK?本当か?
結局は、部分適用したい単位ごとに()で囲わないといけないということか。やっぱりHaskellみたいにフリーダムじゃないのね。とはいえ、思っていたよりは使えそうかも。安心した。部分適用するかもしれない関数の引数は、なんでもかんでもバラバラに()で切り刻んでおけばOK?本当か?
コメント
そこはちょっと誤解がある気がします。
Haskellでも、
f(x, y) = x + y
と定義した場合(タプルを引数に取る関数)、
f 3
のような部分適用はできません。Scalaで
def f(x: Int, y: Int) = x + y
とした場合に、
f(3) _
と部分適用できないのも、それと同様の話です。要は
f x y = x + y
に対応するのが
Scalaの
def f(x: Int)(y: Int) = x + y
で、
f(x, y) = x + y
に対応するのが
def f(x: Int, y: Int) = x + y
と考えれば良いかと(細かい違いは色々ありますが)。