Hatena::ブログ(Diary)

北海道苫小牧市出身のPGが書くブログ RSSフィード Twitter

Fork me on GitHub

2012-02-02(木)

Scalaのvalとvarとdef

valは定数、varは変数、defはコード、というのが大まかな理解になる。これらは名前空間を共有しているので、定数、変数、コードに同じ名前をつけることはできない。ただし、JSやPythonのように単純にフィールドにメソッドオブジェクトが入っていると思うとハマる部分があるので、その辺を含めて調べたことをメモ。(なお、このエントリは 2.9.1.final での挙動について書いている。)


クラス定義のvar、def、val はメソッド

valとvarはfinal付き、final無しの変数として解釈されると思われるが、クラスのフィールドとして定義した場合にはJVM上ではメソッド経由でアクセスすることになる。ので、abstract defをvarやvalでオーバーライド*1したりできる。

class ValVarDef {
  val x = 1
  var y = 2
  def z = 3
}
% javap -private ValVarDef
public class ValVarDef extends java.lang.Object implements scala.ScalaObject{
    private final int x;
    private int y;
    public int x();
    public int y();
    public void y_$eq(int);
    public int z();
    public ValVarDef();
}

メソッドと関数型のフィールドは違う

JSなどと違い、def hoge(x: String): Int (メソッド)とdef hoge: String => Int (関数オブジェクトのフィールド)はまったく違うもの。

// 名前付き引数で呼べる
scala> def hoge(x: String): Int = x.length
hoge: (x: String)Int

scala> hoge("abcde")  // ←一見同じ振る舞いをするように見える
res140: Int = 5

scala> hoge(x = "abcde")
res141: Int = 5

// 名前付き引数で呼べない
scala> def hoge: String => Int = x => x.length
hoge: String => Int

scala> hoge("abcde")  // ←一見同じ振る舞いをするように見える
res142: Int = 5

scala> hoge(x = "abcde")
<console>:11: error: reassignment to val
              hoge(x = "abcde")
                     ^

// 引数には自動的に名前がつく (コメント欄参照)
scala> hoge(v1 = "abcde")
res3: Int = 5

JVMのレベルで見ると、前者はメソッドに直接処理を記述したもので、後者は処理を記述されたFunctionNオブジェクト(ここでは MyTest$$anonfun$hoge2$1)を返すメソッドになる。

class MyTest {
  def hoge1(x: String): Int = x.length
  def hoge2: String => Int = x => x.length
}
public class MyTest extends java.lang.Object implements scala.ScalaObject{
    public int hoge1(java.lang.String);
    public scala.Function1 hoge2();
    public MyTest();
}

public final class MyTest$$anonfun$hoge2$1 extends scala.runtime.AbstractFunction1 implements scala.Serializable{
    public static final long serialVersionUID;
    public static {};
    public final int apply(java.lang.String);
    public final java.lang.Object apply(java.lang.Object);
    public MyTest$$anonfun$hoge2$1(MyTest);
}

defの定義で、引数なしと空括弧は微妙に違う

def hoge = 1 と def hoge() = 1 は振る舞いが微妙に違う。特に関数オブジェクトが入っている場合。

scala> def func1 = (_: Int) + 1
func1: Int => Int

scala> def func2() = (_: Int) + 1
func2: ()Int => Int

// 引数無しで定義すると"func1()"では呼び出せない
scala> func1
res136: Int => Int = <function1>

scala> func1()
<console>:9: error: not enough arguments for method apply: (v1: Int)Int in trait Function1.
Unspecified value parameter v1.
              func1()
                   ^

// 空括弧で定義すると"func2()"でも"func2"でも呼び出せる
scala> func2
res138: Int => Int = <function1>

scala> func2()
res139: Int => Int = <function1>

// func1は括弧付きで呼び出せないので、意図したように解釈される
scala> func1(5)
res131: Int = 6

// func2を5を引数として呼び出したと解釈されるため、エラーとなる
scala> func2(5)
<console>:9: error: too many arguments for method func2: ()Int => Int
              func2(5)
                   ^

scala> func2()(5)
res135: Int = 6

=> はJVM上では関数オブジェクト

まあそうだよね。

class MyTest {
  def hoge1(x: Int) = x + 1
  def hoge2(x: => Int) = x + 1
  def hoge3(x: () => Int) = x() + 1
}
public class MyTest extends java.lang.Object implements scala.ScalaObject{
    public int hoge1(int);
    public int hoge2(scala.Function0);
    public int hoge3(scala.Function0);
    public MyTest();
}

lazy val はJVM上ではメソッドとして定義される

まあそうだよね。

class MyClass {
  def hoge1: Int = {
    val x = 1
    lazy val y = 1
    x + y
  }

  def hoge2: Int = {
    val x = 1
    lazy val y = 1
    x + y
  }
}
public class MyClass extends java.lang.Object implements scala.ScalaObject{
    public int hoge1();
      Code:
       ....
       24:  invokespecial   #21; //Method y$1:(Lscala/runtime/IntRef;Lscala/runtime/VolatileIntRef;)I
       ....
       27:  iadd
       28:  ireturn

    public int hoge2();
    private final int y$1(scala.runtime.IntRef, scala.runtime.VolatileIntRef);
    private final int y$2(scala.runtime.IntRef, scala.runtime.VolatileIntRef);
    public MyClass();
}

objectはstatic

objectはシングルトンオブジェクトを定義するものだが、JVM上ではstaticとして解釈される互換性のためstaticメソッドも用意される

class  MyTest { val inClass  = 1 }
object MyTest { val inObject = 2 }
% javap MyTest MyTest$
public class MyTest extends java.lang.Object implements scala.ScalaObject{
    public static final int inObject();
    public int inClass();
    public MyTest();
}

public final class MyTest$ extends java.lang.Object implements scala.ScalaObject{
    public static final MyTest$ MODULE$;
    public static {};
    public int inObject();
}

*1:コメント欄も参照

xuweixuwei 2012/02/02 13:52 >defをvarやvalでオーバーライド

以下のように単にdefをvarでoverrideはできないので、ちょっと正確さをかく記述のような

https://gist.github.com/1721515#file_override_var

def hoge: String => Int = x => x.length
も、hoge自体のFunction1オブジェクトで名前つき引数自体は存在する(勝手にv1っていう名前になる)ので以下のような呼び方はできますね(必要になる場面が無いのでやってる人みたことないですけど)

https://gist.github.com/1721515#file_function1


>objectはシングルトンオブジェクトを定義するものだが、JVM上ではstaticとして解釈される。

合ってるんですけど、もっと細かいこというと
"MODULE$という static な field にそのシングルトンのオブジェクトが保持されて、(おそらくJavaとの互換性のために?)そのMODULE$というオブジェクトにアクセスするためのstaticメソッドも用意される(インスタンスメソッドもある)" という感じでしょうか。
逆コンパイルすると、Scala同士の呼び出しでは、コンパイル後は

Predef$.MODULE$.println

というように、static method経由ではなくMODULE$というstatic fieldに直接アクセスするような感じになったと思います。このあたりは仕様で決まってるわけではないので、現在の実装がそうなっているというだけですが

hiratarahiratara 2012/02/02 15:35 アドバイスありがとうございます。


> 以下のように単にdefをvarでoverrideはできないので、

はい、そのことは把握していたのですが、確かにoverrideと言ってしまうと誤解を招きますね。言いたかったのは、abstract def の実装として使えるってイメージでした。


> 勝手にv1っていう名前になる

おおー、なるほど!


> static method経由ではなくMODULE$というstatic fieldに直接アクセスする

API的な観点で書いてしまいましたが、確かに実際に実装が存在するのはインスタンスメソッド側になりそうですね。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/hiratara/20120202/1328155695
リンク元