2012年7月27日金曜日

[Scala] Function 型の実装

前回、関数の型について調べたので、今回はその実装について見てみることにする。

Function系のトレイトは、引数が1つの Function1 トレイトと引数が2つ以上からなる Function2 ~ Function22 までのトレイトでは実装している関数が異なる。 共通しているのは関数を実行する apply と文字列化用の toString。

  def apply(v1: T1): R
  override def toString() = "<function1>"

上は Function1 トレイトの例。他も同様。apply は抽象メソッドで toString は実装付き。

Function1 ではこの他に関数合成に関する compose と andThen が実装付きで定義されている。
一方、Function2 ~ Function22 では、curried と tupled がそれぞれ実装付きで定義されている。 (curry は deprecated されて curried になっている)。

まずは、Function1 から見ていこう。


Function1 トレイトの compose と andThen はこのようになっている。

  /** Composes two instances of Function1 in a new Function1, with this function applied last.
   *
   *  @tparam   A   the type to which function `g` can be applied
   *  @param    g   a function A => T1
   *  @return       a new function `f` such that `f(x) == apply(g(x))`
   */
  def compose[A](g: A => T1): A => R = { x => apply(g(x)) }

  /** Composes two instances of Function1 in a new Function1, with this function applied first.
   *
   *  @tparam   A   the result type of function `g`
   *  @param    g   a function R => A
   *  @return       a new function `f` such that `f(x) == g(apply(x))`
   */
  def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) }

compose は関数の合成を行うメソッド。自関数 f( T1 ) に対して、g( A )=>T1 (要は戻り値の型が自関数の引数と同じ関数)を受け取って、f( g( A ) ) の結果になる関数を作成する。

andThen も関数の合成を行うが適用順序が逆になる。引数が自関数の戻り値と同じ型になる関数gを受け取り、 g(f(A)) の結果になる関数を作成する。

やってみよう。

    val f : Int => Int = (x : Int) => x * 3;
    val g : Int => Int = (x : Int) => x % 3;
    
    // f(g(x)) -> (x % 3) * 3   g を実行した結果にfを実行
    val fg : Int => Int = f.compose(g);
    println("fg= " + fg(5));    
    
    // g(f(x)) -> (x * 3) % 3   fを実行してからgを実行。
    val gf : Int => Int = f.andThen(g);
    println("gf= " + gf(5));


fg= 6 gf= 0

予想通りの結果になった。



次はFunction2~Function22 トレイト で定義されている curried と tupled を見てみよう。

  /** Creates a curried version of this function.
   *
   *  @return   a function `f` such that `f(x1)(x2) == apply(x1, x2)`
   */
  def curried: T1 => T2 => R = {
    (x1: T1) => (x2: T2) => apply(x1, x2)
  }

  /** Creates a tupled version of this function: instead of 2 arguments,
   *  it accepts a single [[scala.Tuple2]] argument.
   *
   *  @return   a function `f` such that `f((x1, x2)) == f(Tuple2(x1, x2)) == apply(x1, x2)`
   */
  def tupled: Tuple2[T1, T2] => R = {
    case Tuple2(x1, x2) => apply(x1, x2)
  }

curried はカリー化された関数を返す。
カリー化 (currying) とは、計算機科学分野の技法の一つ。複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(by Wikipedia)。
要は 1つの引数を受け取って、残りの引数を受け取って結果を返すような関数を返す操作のことらしい。 このあたりはいずれ別途調べてみることにする。

tuppled は引数をタプルにした関数を返す。引数が1つになるのでFunction1 型になる。

とりあえず使ってみる。

    val addFunc : (Int, Int) => Int = (x: Int, y: Int) => x + y;

    val curriedAdd : Int => (Int => Int) = addFunc.curried;
    println( curriedAdd(2)(3));
    
    val tupledAdd : Tuple2[Int, Int] => Int = addFunc.tupled;
    println( tupledAdd( (2,3) ));


5 5

こんな感じ。
「 val curriedAdd : Int => (Int => Int) 」は、普通は「val curriedAdd : Int => Int => Int 」と書けばいいのだが、何が起きているの理解しにくいのであえて括弧付きで書いてある。

タプル化したバージョンも「 tupledAdd( 2,3 ) 」と普通に 2 引数で書けば暗黙的にタプルが生成されて実行できるのだが何をしたのかわからなくなるので明示的にタプルを作って渡している。
このようにタプル化して 1 引数化することで、Function1 にしかない関数合成関数を使えるようになる。



0 件のコメント:

コメントを投稿