2012年8月12日日曜日

[Scala] 部分適用

部分適用。
部分適用された関数は、関数で定義された引数の一部がパラメータとして供給されない式です。
-- 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 < _) 」ぐらいが一番わかりやすいかなと思う。



部分適用で一部のパラメータを決定することで関数の引数の数が減るため、関数のパラメータを減らす操作であるカリー化と混同しやすいそうだがこれらは全く別のものである。次回でカリー化操作を見てみよう。



0 件のコメント:

コメントを投稿