ラベル 関数 の投稿を表示しています。 すべての投稿を表示
ラベル 関数 の投稿を表示しています。 すべての投稿を表示
このページの記事一覧
● 2013年3月2日土曜日 - [Scala] 無名関数の return の仕組み(その2)
● 2013年3月1日金曜日 - [Scala] 無名関数の return の仕組み
● 2013年2月18日月曜日 - [Scala] 無名関数中での return 文
● 2013年1月21日月曜日 - [Scala] 関数リテラルの2つの定義方法
● 2012年7月27日金曜日 - [Scala] Function 型の実装
● 2012年7月25日水曜日 - [Scala] 関数の型

2013年3月2日土曜日

[Scala] 無名関数の return の仕組み(その2)

前回の記事 に引き続き、無名関数中の return 文の仕組みについて調べてみる。
今回は 匿名関数を引数に引き渡して深い階層からリターンしてくる場合を見てみよう。
今回の Scala コードはこれ。

  def main(args:Array[String]) {
    println("main start")
    test();
    println("main finish")
  }

  private def test() {
    val f = () => return;
    println("test start")
    test2(f);  
    println("test finish")   // 実行されない。
  }
 
  def test2(f: () => Unit) {
    println("test2 start")
    f();
    println("test2 finish")  // 実行されない。
  }

------------------
main start
test start
test2 start
main finish

test2() 内の 匿名関数実行で test2()、test() を一気に脱出している。

2013年3月1日金曜日

[Scala] 無名関数の return の仕組み

この前の記事で、Scala の無名関数中での return 文の動きについてみてみた。

今回はその仕組みが Java 的にどうなっているのか見てみる。 JD-Plugin の導入には前回失敗したので、デコンパイルには Java Decompiler の GUI 版を使った。
前回使ったコードはこれ。
  def main(args:Array[String]) {
    println("main start")
    test();
    println("main finish")
  }
  
  private def test() {
    val f = () => return;
    println("test start")
    f();
    println("test finish")   // 実行されない。
  }

------------------
main start
test start
main finish

2013年2月18日月曜日

[Scala] 無名関数中での return 文

無名関数の中で return 文を使うと、関数を抜けるのではなく、 無名関数を定義した関数自体を抜けてしまう。
  def main(args:Array[String]) {
    println("main start")
    test();
    println("main finish")
  }
  
  private def test() {
    val f = () => return;
    println("test start")
    f();
    println("test finish")   // 実行されない。
  }

------------------
main start
test start
main finish

test() が f() 呼び出しで return していることがわかる。
関数なのだから なんとなく 無名関数 だけを return しそうな気がするので 気を付ける必要がある。

2013年1月21日月曜日

[Scala] 関数リテラルの2つの定義方法

Scala で関数リテラルを定義する場合、通常はこのような形式で定義する。
  val a : Function2[String, String, Unit] = (x,y) => {
    val xx = x + y;
    println(xx);
  }
  a("abc", "def")

--------------------
abcdef

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 にしかない関数合成関数を使えるようになる。



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系クラスの実装を確認してみよう。