2012年7月25日水曜日

[Scala] 関数の型

たとえば、Int型 の引数をとってその倍を返す関数はこのような感じになる。

  val f : (Int) => Int =  (x : Int) => x*2;
  println(f(10))

つまり、「 (Int) => Int 」が変数 f の型になる。
関数を実行するときには、f(10) になるのだが、例によってapply が省略されているので、 省略せずに書くと、f.apply(10) になる。もちろん結果は同じ。

関数に型があるということは関数もオブジェクトなわけで、実際、引数の数に応じて Function1 ~ Function22 というクラスが用意されている。つまり、

    val f2 : Function1[Int,Int] =  (x : Int) => x*2;
    println(f2(10))

というように書くこともできる。むしろこちらが正式な書き方で「(Int) => Int」のほうがシンタックスシュガーにあたる。
「 Function1[Int,Int] 」は引数が1つで Int型、戻り値も int であることを表している。 Int型の引数を2つとって戻り値がDoubleの場合はこうなる。

var f: (Int, Int) => Double = (x: Int, y: Int) => (x + y) / 2.0;
var f2: Function2[Int,Int,Double] = (x: Int, y: Int) => (x + y) / 2.0;

Function22まではあるが、Function23 はない。そうなると引数を23個にしてみなくなるのが人間というものだろう。

    val f23 : (
        Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
        Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
        Int, Int, Int
        ) => Int = null;

type Function23 is not a member of package scala

Function23 が見つからないそうだ。 普通に引数が23個のメソッドも定義できないのだろうか?

  def f23(
         x1:Int,  x2:Int,  x3:Int,  x4:Int, x5:Int, 
         x6:Int,  x7:Int,  x8:Int,  x9:Int, x10:Int,
        x11:Int, x12:Int, x13:Int, x14:Int, x15:Int, 
        x16:Int, x17:Int, x18:Int, x19:Int, x20:Int,
        x21:Int, x22:Int, x23:Int
      ) : Int = 0;

定義できた。どうなってるのだろう。

    val f = f23 _;

missing arguments for method f23 in object FuncTest; follow this method with `_' if you want to treat it as a partially applied function

メソッドとして定義することはできるが、それをオブジェクトとして取り扱うことができないということのようだ。引数が 22 個までなら上のコードで問題ない。

ちなみにメソッドとして定義されているものを関数オブジェクトとして取り扱うときにはこのようにメソッド名の後ろに「 _ 」をつける。部分適用を使って関数として取り出しているような感じ



Function の実装を確認してみよう。

@annotation.implicitNotFound(msg = "No implicit view available from ${T1} => ${R}.")
trait Function1[@specialized(scala.Int, scala.Long, scala.Float, scala.Double) -T1, @specialized(scala.Unit, scala.Boolean, scala.Int, scala.Float, scala.Long, scala.Double) +R] extends AnyRef

見にくいのでアノテーションを取っ払うと、

trait Function1[-T1, +R] extends AnyRef

関数の引数に対応する型パラメータが反変、戻り値に対応する型パラメータは共変になっている。 他の FunctionXX も引数に相当する型パラメータが増えているだけで基本は同じ。

ところで、Function1 では引数は @specialized アノテーションで、Int、Long、Float、Double を特別扱いしているのに対して、Function2 で特別扱いしているのは、Int、Long、Double でFloatが抜けている。
戻り値は Unit、Boolean、Int、Long、Float、Double で同じ。
一方、Function3 以降では引数、戻り値ともに特別扱いなし。

@specialized アノテーション はコンパイルするとクラス数が増えるのでよく使うと思われるものに限定しているのだろう。

次回は、Function系クラスの実装を確認してみよう。



0 件のコメント:

コメントを投稿