Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

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:コメント欄も参照

Scalaの空括弧とUnit

Unit周りでハマったのでメモ。

まず、() => Any という型はあるが、()という型はない。

scala> def id(x: () => Int): () => Int = x
id: (x: () => Int)() => Int

scala> def id(x: ()): () = x
<console>:1: error: '=>' expected but ')' found.
       def id(x: ()): () = x
                   ^

Unit型の唯一の要素が()である。

scala> ().isInstanceOf[Unit]
res67: Boolean = true

() => Any は Unit => Any とは違う。

// 型が違って代入できない
scala> val emptyParentheses: () => Any = { () => 10 }
emptyParentheses: () => Any = <function0>

scala> val unit: Unit => Any = emptyParentheses
<console>:17: error: type mismatch;
 found   : () => Any
 required: Unit => Any
       val unit: Unit => Any = emptyParentheses
                               ^
// 型が違って代入できない
scala> val unit: Unit => Any = {(u: Unit) => 20}
unit: Unit => Any = <function1>

scala> val emptyParentheses: () => Any = unit
<console>:17: error: type mismatch;
 found   : Unit => Any
 required: () => Any
       val emptyParentheses: () => Any = unit
                                         ^

が、Unit => Any は空括弧で呼び出すことができる。

scala> def one(u: Unit): Int = 1
one: (u: Unit)Int

scala> one(())
res68: Int = 1

// これ、なんでエラーにならないのかわからず (Value Discardingから??)
scala> one()
res69: Int = 1

scala> one
<console>:18: error: missing arguments for method one in object $iw;
follow this method with `_' if you want to treat it as a partially applied function
              one
              ^

// これは、要請がUnit型なら{ e; () }に変換するルール(Value Discarding)より
scala> one("DUMMYYY")
res71: Int = 1

// これもValue Discarding から??
scala> one(1, "XXX", Nil)
res72: Int = 1