部分適用。
部分適用された関数は、関数で定義された引数の一部がパラメータとして供給されない式です。
-- by プログラミング Scala
いわゆるオライリーの Scala 本によるとこういうことらしい。
関数を、「_(アンダースコア)」付きで呼び出してパラメータとして未決定のまま取り出す操作を部分適用と呼ぶ。「一部のパラメータのみを決定したもの」ではない。つまり、全部未定義でも部分適用に該当する。
val fpow : (Double, Double) => Double = Math.pow _;
println( fpow(2.0, 3.0));
8.0
ここの「 Math.pow _ 」が部分適用に相当する。
なお、 fpow(2.0, 3.0) は関数実行に見えるが、
apply の回で書いたように fpow.apply(2.0, 3.0) の省略形になっている。
部分適用というくらいなので、一部のパラメータを決定することもできる。
val square : (Double) => Double = Math.pow(_, 2);
println( square(3.0));
9.0
引数を一つ確定したので、square は引数がひとつの関数になっている。たぶん部分適用という言葉から主にイメージする使い方はこの使い方だと思う。
上の例では変数の型を明示したがこれを省略した場合には、「_」に型指定が必要になる。
val square1 = Math.pow(_:Double, 2);
型指定をしないと次のようなエラーになる。
val square1NG = Math.pow(_, 2); // --> NG!
missing parameter type for expanded function
((x$5) => Math.pow{<null>}(x$5{<null>}, 2{<null>}){<null>})
{<null>}
変数の型か「_」の型指定で明示的に省略されているパラメータの型を与える必要があるようだ。
ところで部分適用は匿名関数を定義するのとどう違うのだろうか。
val fpow2 : (Double, Double) => Double
= (x : Double, y : Double) => Math.pow(x, y);
とか、
val square2 : (Double) => Double
= (x : Double) => Math.pow(x, 2);
と書いたのとの違いが今一つ見えない。
書き方の違いだけ?わざわざ匿名関数を作らないでも「_」だけ書けばいいですよ、ということなのだろうか?
「_」の省略
基本的にメソッドや関数に「_」がついているものは部分適用が行われている。しかし、「_」は状況によっては省略することができる。
以下は、Scala 本の例。
List("aaa", "bbb").map(println)
ここで TraversableLike トレイトの map の定義は、
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
で、引数が一つの関数になっているので、map(println _) と書かずに map(println) と書くだけで部分適用が使用される。
別に、「_」一つくらいなら構わないと思うのだが。
同じ理由でこのようなコードもかける。
val fpow : (Double, Double) => Double = Math.pow;
この記事の最初に出てきた例では実は「_」は省略できる。変数の型で引数を2つ取る関数であることを明示しているので、 Math.pow の後ろに「_」をつけなくても部分適用が行われる。
ここでは変数の型が明示されていることがポイントなので、変数の型を明示しないと「_」は省略できない。
val fpowOK = Math.pow _; // --> OK
val fpowNG = Math.pow; // --> NG
インスタンスメソッドへの部分適用
部分適用は、インスタンスメソッドに対しても適用できる。
val secondToXofHello : Int => String = "HELLO".substring(1,_);
println(secondToXofHello(4));
およそ実用性のある例とは思えないが、このようにして特定インスタンスのメソッドへ部分適用ができる。
こんなこともできる。
val l = List(1,2,3,4).filter(2 <)
println( l )
List(3, 4)
「 < 」はRichInt クラスの引数一つのメソッドなので「_」を省略して記述できる。
いろいろ省略しないで書くなら、「 ...filter(2.<(_)) 」 となる。
個人的には、「 ...filter(2 < _) 」ぐらいが一番わかりやすいかなと思う。
部分適用で一部のパラメータを決定することで関数の引数の数が減るため、関数のパラメータを減らす操作であるカリー化と混同しやすいそうだがこれらは全く別のものである。次回でカリー化操作を見てみよう。