S-JIS[2010-12-12/2015-09-22] 変更履歴

Scala def

Scalaのメソッドの定義方法に関するメモ。

「関数」と「メソッド」について


メソッドの定義方法

メソッドはdefで定義する。[2010-10-23]
(とりあえず、defで定義するものは「メソッド」と呼ぶことにする)

メソッドはclassobjecttrait内に定義する。
メソッド内に新しいメソッド(関数)を定義することも出来る。

REPLでは、クラスの外に関数を定義することが出来る。(どのクラスにも属さないグローバルなメソッドのような扱い)

Scala Java 備考
def メソッド名(引数名: 型, …) {
	〜
}
void メソッド名(型 引数名, …) {
	〜
}
戻り値なしのメソッド。
(実際はUnitが指定されているのと同じ)
def メソッド名(引数名: 型, …): Unit = {
	〜
}
戻り型がUnitだと、戻り値が無い事を示す。
しかし「: Unit =」は省略可能。
def メソッド名(引数名: 型, …): 戻り型 = {
	〜
}
戻り型 メソッド名(型 引数名, …) {
	〜
}
戻り値ありのメソッド。
複数の値を返すにはタプルを使う
def メソッド名(引数名: 型, …) = {
	〜
}
戻り値によって型が推定できる場合は、
戻り型は省略可能。
def メソッド名(引数名: 型, …) = {
	〜
}: 戻り型
def メソッド名(引数名: 型, …): 戻り型 = {
	〜
}: 戻り型
戻り型をメソッド本体の後ろに書くことも出来る。[2010-12-25]
ただしこれは本体の式の戻り型を指定しているのであって、メソッド自体は型推論によってその型になっているに過ぎないようだ。
def メソッド名(引数名: 型, …) = 式
  メソッド本体が一つの式の場合、波括弧「{}」も省略可能。
(実際は逆で、複数の式を書きたい場合にブロック式を利用しているだけ。[2011-07-18]
def メソッド名(引数名: 型 = 初期値, …): 戻り型 = {
	〜
}
  Scala2.8では、引数に初期値を指定することが出来る。[2010-12-18]
これにより、引数の個数を変えた同名メソッド(オーバーロード)を定義しなくてもよくなる。
ちなみに、型を省略することは出来ない。(初期値の型からの推論はしてくれない模様^^;)
def メソッド名(引数名: =>型) = 式
  型の前に「=>」を付けると、名前渡しになる。[2011-07-18]
def メソッド名(引数名: 型*): 戻り型 = {
  for (変数 <- 引数名) {
    println(変数)
  }
  〜
}
戻り型 メソッド名(型... 引数名) {
  for (変数 : 引数名) {
    System.out.println(変数);
  }
  〜
}
可変長引数(repeated parameters:引数の数が可変)の例。[2010-12-30]
Javaでは型の後ろにピリオドを3つ付けるが、Scalaでは「*」を付ける。
その引数の実際の型は、Javaでは配列となるが、ScalaではSeq
呼び出し方
def メソッド名 = 本体
  引数リストを書かないメソッドも定義できる。[2011-01-08]
このメソッドを呼び出す場合、「()」は付けない
class クラス名 extends 親クラス {
  override def メソッド名(引数) = 本体
}
class クラス名 extends 親クラス {
  @Override 戻り型 メソッド名〜
}
抽象メソッドでないメソッド(処理本体が書かれているメソッド)をオーバーライドする際は、メソッド定義の前にoverrideを付ける必要がある。[2011-01-04]
class A {
  def value(): AnyRef = null
}

class S extends A {
  override def value(): String = "empty"
}
  Javaと同様に、共変戻り値型が指定できる。[2011-01-04]
(メソッドをオーバーライドした際、戻り型を、親メソッドの型の子クラスにすることが出来る)
  def メソッド名[A](引数) = 本体
  def メソッド名[A, B](引数) = 本体
  public<A> 戻り型 メソッド名(引数) {
  public<A, B> 戻り型 メソッド名(引数) {
メソッドだけで使用する型引数(ジェネリクス)は、メソッド名の後ろで宣言する。[2015-09-22]

識別子(メソッド名・変数名)に使える文字・記号
複数の引数リスト
「= 本体」を書かないと、抽象メソッドになる
implicit def(暗黙の型変換)


名前渡し

メソッド(関数)の引数を「=> 戻り型」とする(「=>」の左側には何も書かない)と、名前渡し(call-by-name)になる。[/2011-07-18]

名前渡しにすると、関数(関数リテラル)も渡せるし、普通の値も渡せるようになる。
名前渡しした関数は、実際に使われるまで評価(実行)されない。

scala> def test(b:Boolean, f: => String) = {
     |   if(b) "nothing" else f + "def"
     | }
test: (b: Boolean,f: => String)java.lang.String

scala> test(true, { println("called"); "abc" })
res138: java.lang.String = nothing

scala> test(false, { println("called"); "abc" })
called
res139: java.lang.String = abcdef

scala> test(false, "zzz")
res140: java.lang.String = zzzdef
これはメソッド(関数)の引数(呼び出し方)としての機能であり、関数の型ではない。
したがって 、この書き方はvarでの変数の型としては記述できない。
scala> var f: => String = _
<console>:1: error: identifier expected but '=>' found.
       var f: => String = _
              ^

メソッド・関数から値を返す方法

戻り値を返すには、JavaやC言語ではreturn文を使う。[2010-10-23]
Scalaでもreturnを使うことが出来るが、一番最後の式の演算結果がメソッド(関数)の戻り値となるので、returnは省略できる。

Scala Java 備考
def sum(a: Int, b: Int) = {
  return a + b
}
def sum(a: Int, b: Int) = {
  a + b
}
public int sum(int a, int b) {
  return a + b;
}
 
def print(n: Int) : Int = {
  println(n)
  n + 1
}
public int print(int n) {
  System.out.println(n);
  return n + 1;
}
ブロック式

Scalaでは「return」を明示的に書くことは少ないようだ。
メソッドの途中でreturnによって戻るのもScalaではあまり行わないらしい。


メソッド・関数を呼び出す方法

メソッドの呼び出し方の基本は、Javaと同様。[2010-10-23]
オブジェクトの後にピリオドで区切ってメソッド名を書き、丸括弧で引数を囲む。

	オブジェクト変数.メソッド名(引数, …)
	関数名(引数, …)

Scalaでは、オブジェクトとメソッドの間のピリオド「.」を省略することが出来る。[2010-12-18]

	str.substring(1, 3)
	str substring(1, 3)

引数の無いメソッドの場合、ピリオドの他に丸括弧も省略できる。[2010-10-23]

//def toString() = 〜
	obj.toString()
	obj.toString
	obj toString()
	obj toString

引数リストの無いメソッドの場合、丸括弧を付けずに呼び出す。[2011-01-08]
丸括弧を付けるとエラーになるが、ピリオドを省略することは出来る。

//def method = 〜
	obj.method
	obj method

引数が1個のメソッドも丸括弧も省略できるが、“括弧のみ省略”は出来ない。[2010-12-12/2011-01-09]
ピリオドと括弧を付けない書き方を中置記法と言うらしい。(演算子を真ん中に置いているように見えるからかな?)

	str.eq("abc")
	str eq("abc")
	str.eq "abc"  //これは出来ない
	str eq "abc"
str.==("abc")
str ==("abc")
str.== "abc"
str == "abc"
n.+(123)
n +(123)
n.+ 123
n + 123
t.==((1,2))
t ==((1,2))
t.== (1,2)  //タプルでは可
t == (1,2)
 
	list.+:("aa")
	"aa" +: list
      末尾がコロン「:」で終わるメソッドでは、ピリオドを付けない場合は左右を入れ替える。[2011-04-17]

また、引数が1個のメソッドの場合、丸括弧の代わりにとげ括弧を使うことが出来る。
(「値を渡す」というより「処理を渡す(記述する)」というニュアンスの時に使用する)

	str.eq("abc")
	str.eq{"abc"}
	str eq{"abc"}
	breakable ( if(true) break )
	breakable { if(true) break }

可変長引数のメソッドを呼び出す方法。[2011-02-26]

Scala Java 備考
val r = メソッド(値, 値, …)
戻り型 r = メソッド(値, 値, …)
可変長引数のメソッドを呼び出す場合、カンマ区切りで値を列挙する。
val args = Seq(値, 値, …)
val r = メソッド(args: _*)
型[] args = { 値, 値, … }
戻り型 r = メソッド(args)
値の入っているSeqを渡したい場合、呼び出し時に「:_*」を付ける。
Javaの呼び出し方

名前付き引数指定

Scala2.8では、引数の変数名を指定してメソッドを呼び出すことが出来る(Named Parameter)。[2010-12-18]

scala> def f(a:Int, b:Int, c:Int) = println(a,b,c)
f: (a: Int,b: Int,c: Int)Unit

scala> f(1, 2, 3)		//引数順に従った呼び出し
(1,2,3)

scala> f(c = 1, a = 2, b = 3)	//引数名を使った呼び出し
(2,3,1)

scala> f(1, c = 2, b = 3)	//途中まで引数順に従い、残りを名前指定にすることも出来る
(1,3,2)

scala> f(1, b = 2, 3)	//引数の位置と名前が一致する場合は、途中でも名前指定可
(1,2,3)

初期値が指定されているメソッドでは、引数名を使うことによって一部分だけ指定することが出来る。

scala> def f(a:Int = 1, b:Int = 2, c:Int = 3, d:Int = 4) = println(a,b,c,d)
f: (a: Int,b: Int,c: Int,d: Int)Unit

scala> f()
(1,2,3,4)

scala> f(11,22,33)
(11,22,33,4)

scala> f(d = 44)
(1,2,3,44)

scala> f(11, c = 123)
(11,2,123,4)

C言語やJavaでは「=」を使った代入はどこでも(メソッド呼び出しの部分でも)使えるので、このように「=」を使って引数名を指定する文法は作れないだろう。
VBA(VisualBasic)では、名前指定する場合は「:=」を使っている。


特殊なメソッド名

Scalaでは、メソッド名そのものに特別な意味・効果のあるものがある。[2010-12-12]

メソッド名 説明
apply applyメソッドを呼び出す際は、メソッド名(とその前に付けるピリオド)を省略することが出来る。
すなわち「obj.apply(n)」=「obj(n)」、「obj.apply(a0, a1, …)」=「obj(a0, a1, …)」である。
apply()の型はメソッドを定義するときに自由に付けられるし、
オーバーロードする(applyというメソッド名で引数の型が違うものを定義する)ことも出来る。
シングルトンオブジェクトのリテラル生成オブジェクトを使ったファクトリーパターン
配列の要素取得
update obj.update(n, v)」というメソッドの呼び出しは、「obj(n) = v」と記述することが出来る。
n,vの型はupdateメソッドを定義するときに自由に付けられる。
配列の要素設定
Mapの値設定

引数が多い場合も適用できる。つまり「obj.update(n0, n1, …, v)」を「obj(n0, n1, …) = v」と書ける。[2010-12-30]
添字部分が0個になるような書き方も出来る。「obj.update(v)」→「obj() = v[2011-01-04]
末尾「_=」 「_=」が末尾に付いたメソッドは、値を代入する書き方で呼び出せる。(条件あり。後述)[2011-01-08]
すなわち「obj.zzz_=(v)」というメソッド呼び出しが「obj.zzz = v」と書ける。
(まるでフィールド(プロパティー)に値をセットするかのような記述でメソッドを呼び出せる。
 つまり「_=」が末尾に付いたメソッドはセッターメソッド(値をセットするメソッド)という意味合いになる)

この書き方をしたい場合、ゲッターメソッド(引数リストの無い同名メソッド)も併せて定義しておく必要がある。
class クラス名 {
  def zzz_=(引数1個) = { 〜 } //セッターメソッド
  def zzz            = { 〜 } //ゲッターメソッド
}
セッターの引数の型とゲッターの戻り値の型は無関係で何でもよく、メソッド名が等しい必要がある。
やる気なら、セッターメソッドは引数の型を変えて複数のメソッドをオーバーロードすることが出来る。
(ゲッターメソッドは引数が無いから、オーバーロードは出来ない)
とは言え、使い道はプロパティーの設定・取得風の書き方をすることだろうから、普通はオーバーロードすることは無いだろう^^;
末尾「: 引数が1個のメソッドを呼び出す際は、ピリオドと括弧を省略することが出来る。例:「a.+(b)」→「a + b[2011-04-12]
ただしメソッド名の末尾がコロン「:」の場合、演算子(メソッド名)の左右を入れ替えて記述する。
例:「a.+:(b)」→「b +: a」、「list.::(a)」→「a :: list」、「c./:(1){ (a,b)=> a+b }」→「(1 /: c){ (a,b)=> a+b }
普通「a + b」と書かれていれば「+メソッド」はaのメソッドだが、「a :: list」なら「::メソッド」はaでなくlistのメソッド。
記号+末尾「=」 記号のみで構成された演算子の末尾に「=」を付けた呼び出しは、代入演算となる。[2011-05-04]
例えば「a += n」という呼び出しは、「a = a + n」と同じ。
この場合、aのクラスには「+」演算子だけ定義されていればよい。(それだけで+=が使えるようになる)
なお、「+=」という演算子(メソッド)が定義されていれば、当然そのメソッドが呼び出される。
unapply
unapplySeq
オブジェクトの中でunapplyまたはunapplySeqという名前のメソッドを定義しておくと、
パターンマッチ(case)でそのオブジェクトを指定できる。[2011-01-09]
unapplyメソッドのことを抽出子(extractor)と呼ぶ。
unary_ 「unary_」に「+-!~」のいずれかを付けると、前置演算子として使えるようになる。[2011-05-04]
case class A(s: String) {  // val a = A("a")に対し
  def unary_+ = new A("+" + s) //  +a
  def unary_- = new A("-" + s) //  -a
  def unary_! = new A("!" + s) //  !a
  def unary_~ = new A("~" + s) //  ~a
  def unary_* = new A("*" + s) //「*a」とは書けない。「a.unary_*」となる。
}
main オブジェクトの中でmainという名前で引数がString配列(戻り型は何でもよいが、普通はUnit)のメソッドを定義しておくと
アプリケーションをそのメソッドから実行することが出来る。[/2011-01-15]
  def main(args: Array[String]): Unit = { 〜 }
  def main(args: Array[String])         { 〜 }
selectDynamic
updateDynamic
applyDynamic
applyDynamicNamed
Dynamicトレイトを実装して左記のメソッドを記述するとフィールドアクセスのような使い方が出来るようになる。[2013-06-08]
メソッドの実装および使用方法の例

メソッド名(識別子)に使える文字・記号
メソッド名に記号を使った場合の、演算子としての優先順位


関数の型

Scalaでは、関数(メソッド)自体を変数に代入したりメソッドの引数に渡したりすることが出来る。[2010-12-25]
関数を受け取ったり関数を返したりするメソッド(関数)の事を「高階メソッド」「高階関数(higher order functions)」と呼ぶ 。
(→関数リテラル

以下のように定義される関数(メソッド)に対し、

	def 関数名(引数名:引数の型, …) : 戻り型 = 本体

その関数の型は以下のように書く。

	(引数の型, …) => 戻り型

関数の型の例


複数の引数リスト

Scalaでは、引数リストを複数持つ事が出来る。[2010-12-27]
引数リストというのは、「(引数:型, …)」のこと。

引数リストが1個(普通)の例 引数リストが2つの例
scala> def f(a:Int, b:Int) = a + b
f: (a: Int,b: Int)Int

scala> f(1, 2)
res1: Int = 3
scala> def f(a:Int)(b:Int) = a + b
f: (a: Int)(b: Int)Int

scala> f(1)(2)
res21: Int = 3

カリー化


例えば、回数と処理(関数)を受け取って、その回数だけ処理を実行する関数を作ってみる。

説明
scala> def myloop(n: Int, f: => Unit) = {
     |   var m = n
     |   while (m > 0) {
     |     f
     |     m -= 1
     |   }
     | }
myloop: (n: Int,f: => Unit)Unit
scala> myloop(3, { println("abc") })
abc
abc
abc
引数nに回数、引数fに関数(この例では、引数なしで戻り値もなし(Unit))を渡す。
scala> def myloop(n: Int)(f: => Unit) = {
     |   var m = n
     |   while (m > 0) {
     |     f
     |     m -= 1
     |   }
     | }
myloop: (n: Int)(f: => Unit)Unit
scala> myloop(3)({ println("abc") })
abc
abc
abc
引数リストを2つに分割してみた。
関数本体の中身は何も変わっていない。
scala> myloop(3){ println("abc") }
abc
abc
abc
scala> myloop(3) {
     |   println("abc")
     | }
abc
abc
abc
関数(メソッド)を呼び出す際、引数が1個しかない引数リストでは、丸括弧を省略できる
すると、なんだかmyloopという新しい文(ステートメント)が出来たような感じになる。
scala> @annotation.tailrec
     | def myloop(n: Int)(f: => Unit):Unit =
     |  if (n > 0) {
     |    f
     |    myloop(n - 1)(f)
     |  }
myloop: (n: Int)(f: => Unit)Unit
ちなみに、(while式でなく)再帰呼び出しを使った方が関数型プログラミングっぽいかもしれない。

valによるオーバーライド

defによって定義されたメソッドを、valによってオーバーライドすることが出来る。[2011-10-16]

class A {
  def zzz = "zzz" //あるいは丸括弧つきで「def zzz() = "zzz"」
  def print() = zzz
}

class B extends A {
  override val zzz = "bbb"
}

何故これが出来るかと言うと、(publicやprotectedの)valはゲッターメソッドのdefが生成されるから。
そのため、ゲッターメソッドのdefがオーバーライドされた状態となる。


ただし、オーバーライドされたvalが親クラスのコンストラクターから参照される(呼ばれる)ときは注意が必要。

class A {
  def zzz = "aaa"
  println("zzz=" + zzz) //コンストラクター内で表示
}

class B extends A {
  override val zzz = "bbb"
}
scala> new A
zzz=aaa
res18: A = A@1d59860

scala> new B
zzz=null
res19: B = B@1958fc2

これは何故かと言うと、valの値はコンストラクターで初期化される為、親クラスのコンストラクター実行時はまだ子クラスのコンストラクターが呼ばれていないので初期化されていないから。

上記のクラスBは、実態(Java)としては以下のような形になっている。

public class B extends A {
	private String _zzz; //初期化されるまではnull

	public B() { //コンストラクター
		super(); //親クラスのコンストラクター呼び出し

		this._zzz = "bbb";
	}

	@Override
	public String zzz() {
		return this._zzz;
	}
}

自分のコンストラクターでフィールドを初期化する前にオーバーライドされたzzz()が呼ばれるので、初期化されていない状態のthis._zzzが返される。

このような初期化順序に依存するコーディングはなるべくしない方がいいと思う。
が、ScalaのSwingではうかうかしているとこういう状態がよく発生する(苦笑)


このような場合は、lazy valを使うと解決できる。

class A {
  def zzz = "aaa"
  println("zzz=" + zzz) //コンストラクター内で表示
}

class B extends A {
  override lazy val zzz = "bbb"
}
scala> new A
zzz=aaa
res20: A = A@10f5650

scala> new B
zzz=bbb
res21: B = B@1858df1

lazy valは、呼び出された時点で初期化されているかどうかチェックし、初期化されていなければ値を準備してそれを返す為。

“lazy”なくせに、コンストラクターより早く動く(笑)


暗黙変換(implicit conversion)

Scalaには、暗黙の型変換(implicit conversion)と呼ばれる仕組みがある。[2010-12-26]

implicit def 関数名(引数:変換元クラス) : 変換後クラス = 〜

こういった関数(メソッド)を定義してインポートしておくと、変換元クラスのインスタンスに対して変換後クラスのメソッドが呼べるようになる。
Intの値RichIntのメソッドが呼べたり、配列(Array)ArrayOpsのメソッドが呼べたりするのは、自動的にインポートされるPredefの中で暗黙変換のメソッドが定義されているから。


暗黙変換の仕組み

例えば「val n = -12; n.abs」という、Intの変数nに対するRichIntのメソッドabsの呼び出しは、以下のようになっている。

  1. Intクラスにはabsというメソッドが無い。
  2. インポートされている暗黙変換の変換後のクラスから、absというメソッドがあるクラスを探す。
    IntからRichIntへの変換はPredefで以下のように定義されており、(Predefなので自動的に)インポートされている。
    implicit def intWrapper(x: Int) = new runtime.RichInt(x)
  3. そこで、「n.abs」という呼び出しは、「intWrapper(n).abs」という呼び出しに置き換えられる。
  4. つまり、実際には、IntをラップしたRichIntインスタンスが作られ、そのabsメソッドが呼ばれることになる。

これらはコンパイル時に行われる。つまり、実行時には呼び出される変換メソッドは固定されている。


Intから自前のクラスへの暗黙変換は、以下の様に作れる。

scala> class M(val n:Int) {
     |   def zzz() = "zzz" + n
     | }
defined class M

scala> implicit def int2m(n:Int) = new M(n)
int2m: (n: Int)M

scala> 123.zzz()
res1: java.lang.String = zzz123

暗黙変換では引数の型戻り値の型が重要で、メソッド名はどうでもいい。
(intをラップするのでintWrapperとか、FooからBarへ変換するので「Foo to Bar」を略して「foo2bar」とか)

なお、暗黙変換の複数の候補同じメソッドがあると、解決できなくてエラーになる。

scala> class M(val n:Int) {
     |   def abs() = "my abs " + n
     | }
defined class M

scala> implicit def int2m(n:Int) = new M(n)
int2m: (n: Int)M

scala> 123.abs
<console>:31: error: type mismatch;
 found   : Int
 required: ?{val abs: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method int2m in object $iw of type (n: Int)M
 and method intWrapper in object Predef of type (x: Int)scala.runtime.RichInt
 are possible conversion functions from Int to ?{val abs: ?}
       123.abs
       ^

どちらかだけを有効にしたい場合、「対象となるメソッドを含まないクラス」を返すような同名の関数を定義してやれば無効化できる。[2011-03-05]

scala> implicit def intWrapper = null
intWrapper: Null

scala> 123.abs
res4: java.lang.String = my abs 123
トレイト内の暗黙変換を無効にする例 [2011-04-17]
通常 無効化 上書き 備考
trait T {
  implicit def s2z(s:String) = new {
    def zzz() = s + "zzz"
  }
}
 
class A extends T {



  def f() = "aaa".zzz
}
class B extends T {
  implicit def s2z(s:String) = null


  def f() = "aaa".zzz
}
class C extends T {
  override implicit def s2z(s:String) = new {
    def zzz() = "zzz" + s
  }
  def f() = "aaa".zzz
}
無効化する場合、overrideキーワードは不要なようだが
メソッド名は合わせる必要がある。
scala> new A().f
res2: java.lang.String = aaazzz
<console>:8: error: value zzz is not a member of java.lang.String
         def f() = "aaa".zzz
                         ^
scala> new C().f
res4: java.lang.String = zzzaaa
 

pimp my library

例えば自分のクラスからStringへ変換するメソッドは、自分のクラス内にtoString()メソッドを作ればいい。
逆にStringから自分のクラスへ変換したい場合は、Stringに対して「to自クラス()」というメソッドを用意したいところだがStringクラスは修正できない。
そこで暗黙変換を定義してインポートしてやれば、「to自クラス()」という呼び出しが出来るようになる。

(Javaではこういった事が出来ないので、valueOf(String)といったstaticメソッドを自クラス内に用意するような方法を採る)

この(自分で修正することのできない)既存クラス後からメソッドを追加する(ように見せる)為の実装方法を「pimp my library」パターンという。

REPLで実験:

scala> class M(val n:Int) {
     |   override def toString() = n.toString
     | }
defined class M

scala> class MyString(val s:String) {
     |   def toM() = new M(s.toInt)
     | }
defined class MyString

scala> implicit def string2MyString(s: String) = new MyString(s)
string2MyString: (s: String)MyString

scala> "123".toM()
res1: M = 123

コンパイルして実験:

package sample

class MyClass(val n:Int) {
  override def toString() = "MyClass:" + n
}

class MyString(val s:String) {
  def toMyClass() = new MyClass(s.toInt)
}

object MyString {
  implicit def string2MyString(s:String) = new MyString(s)
}

object ImplicitConversion {
  def main(args:Array[String]) {
    import sample.MyString._	//暗黙変換のメソッドをインポート

    val m = "123".toMyClass()	//暗黙変換が発生
    println(m)
  }
}

既存クラスに対するメソッドを1つだけ定義したいような場合は、(変換クラスを作らなくても)以下のような関数定義だけで作れる。[2011-03-05]

//StringisUpper()メソッドを追加する
scala> implicit def stringIsUpper(s:String) = new{ def isUpper() = s.forall(_.isUpper) }
stringIsUpper: (s: String)java.lang.Object{def isUpper(): Boolean}

scala> "abc".isUpper
res1: Boolean = false

scala> "ABC".isUpper
res2: Boolean = true

暗黙クラス(implicit class)

Scala2.10ではimplicit class(SIP-13)が導入され、既存クラスにメソッドを追加する形式の暗黙変換を作りやすくなった。[2013-06-08]
pimp my libraryパターンを使わなくて済むようになった)

implicit class MyString(val s: String) extends AnyVal {
  def toMyClass() = new MyClass(s.toInt)

  def isUpper() = s.forall(_.isUpper)
}

参考: kmizuさんのScala 2.10.0 M3の新機能を試してみる(2) - SIP-13 - Implicit classes


implicit classを定義する際に「extends AnyVal」を付けて値クラスにしておくと、暗黙変換を呼び出す側で余計なインスタンスが作られない。

暗黙クラスが値クラスでない場合、呼び出し側の実体は以下のようなコード(イメージ)になる。

    "ABC".isUpper	//呼び出す例
↓
    new MyString("ABC").isUpper()	//呼び出しコードの実体のイメージ

呼び出し毎にMyStringインスタンスが作られる。(JavaVMレベルでは(実行時に)、最適化によってインスタンスが作られないようになる事もあるらしいが)

これに対し、値クラスになっていると以下の様なクラスやメソッド(イメージ)が用意され、呼び出し毎のインスタンスは作られない。

public class MyString {
  public boolean isUpper$extension(String $this) {
    return 〜;
  }

  public static final MyString MODULE$ = new MyString();
}
  public static String MyString(String s) {
    return s;
  }
    MyString.MODULE$.isUpper$extension(MyString("ABC"))	//呼び出しコードの実体のイメージ

暗黙引数

Scalaには、暗黙のパラメーター(implicit parameter)という機能がある。[2010-12-30]

def メソッド名(通常の引数:型, …)(implicit 暗黙引数:型) : 戻り型 = 〜

引数の前にimplicitを付けると、その引数は暗黙の引数として扱えるようになる。
implicitは引数リストの先頭の変数にしか付けられないので、引数リストを複数持つ形で使われることが多いのではないかと思う。

「暗黙の引数である」という指定がされた引数は、呼び出し側で省略することが出来る。
その場合、その引数の型に応じた暗黙の値がセットされる。暗黙の値が見つからなければコンパイルエラーになる。
暗黙の値は、valobject等の前にimplicitを付けて定義する。

備考
scala> def f(n:Int)(implicit m:Int) = n + m
f: (n: Int)(implicit m: Int)Int

scala> implicit val iv: Int = 123 //暗黙の値を定義
iv: Int = 123

scala> f(5)
res1: Int = 128
関数fの引数mはInt型の暗黙の引数なので、呼び出し時にmが省略された場合、Int型の暗黙の値が使用される。
なお、それが決まる必要があるのは引数が省略されているメソッド呼び出しのコンパイル時点なので、
メソッド定義時点は関係ない。
scala> f(1)(2)
res2: Int = 3
implicitが指定されている引数を明示的に指定することも問題ない。
scala> def f(n:Int)(implicit m:Long) = n + m
f: (n: Int)(implicit m: Long)Long

scala> implicit val iv = 123
iv: Int = 123

scala> implicit val lv = 456L
lv: Long = 456

scala> implicit val fv = 987f
fv: Float = 987.0

scala> f(4)
res3: Long = 460
暗黙の値は型に応じて決まるので、別々の型で暗黙の値を定義しておいても区別される。
暗黙の値の変数名も何でもよい。
scala> def f(n:Int)(implicit m:Int) = n + m
f: (n: Int)(implicit m: Int)Int

scala> implicit val iv = 123
iv: Int = 123

scala> implicit val v2 = 999
v2: Int = 999

scala> f(1)
<console>:11: error: ambiguous implicit values:
 both value v2 in object $iw of type => Int
 and value iv in object $iw of type => Int
 match expected type Int
       f(1)
        ^
メソッド呼び出し時点でその型の暗黙の値が2つ以上あるとコンパイルエラーになる。
class Sample {
  protected def f(n:Int)(implicit m:Int) = {
    if (n > 0) n else m
  }
}

class Sample1 extends Sample {
  implicit val ImplicitInt = -1

  def g(n:Int) = super.f(n)
}

class Sample0 extends Sample {
  implicit val ImplicitInt = 0

  def g(n:Int) = super.f(n)
}
scala> new Sample1().g(0)
res7: Int = -1

scala> new Sample0().g(0)
res8: Int = 0
暗黙の引数(implicit 引数名:型)とメソッドの引数のデフォルト値引数名:型 = 値)との違いは、
デフォルト値の方はメソッド定義側で決めるのに対し、
暗黙の引数はメソッドの呼び出し側が決められる、ということ。

Intだとあまり使い道が無いかもしれないが、
ファクトリーオブジェクトを暗黙に指定したりする使い方が想定されているようだ。
あと、ClassTagでよく使用される模様。[/2013-06-08]
implicit val v = 123
implicit var v = 123
implicit def f = 123
implicit def f() = 123 ←これは暗黙の値にはならない
暗黙の値は、val以外にvarでもいいし、引数リスト無しのdefでもいい。
scala> implicitly[Int]
res11: Int = 123

scala> implicitly[Long]
res12: Long = 456

scala> implicitly[Double]
<console>:12: error: could not find implicit value
 for parameter e: Double
       implicitly[Double]
                 ^
ある箇所での暗黙の値がどうなっているのかは、Predefに定義されているimplicitly関数で調べられる。
(implicitlyは、暗黙に定義されている値を取得するメソッド)

暗黙の値が見つからない場合はエラーになる。

ClassTagを暗黙に取得する例
暗黙変換のシンタックスシュガー(context bound [T: A]、view bound [T <% C]


context boundとview bound

暗黙のパラメーター(implicit parameter)に関して、特別な場合には別の書き方が出来る(シンタックスシュガーがある)。[2011-01-04]

context bound
(Scala2.8)
def メソッド[T](implicit a: A[T]) = {
  〜
}

 
def メソッド[T : A] = {
  〜
}
ClassManifestの例
T:A」と書かれていると「TはAという型」という感じがしてしまうので、
まぎらわしい気がする…。
class クラス[T](implicit a: A[T]) {
  〜
}

 
class クラス[T : A] {
  〜
}
view bound
def メソッド[T](implicit f: T => C) = {
  〜
}

 
def メソッド[T <% C] = {
  〜
}
view boundの例
ジェネリクスの可視境界
class クラス[T](implicit f: T => C) {
  〜
}

 
class クラス[T <% C] {
  〜
}

参考: 武田ソフトさんのView Bound/Context Bound


view bound

view bound(可視境界)は、TからCへの暗黙変換が欲しいときに「T <% C」と書けるというシンタックスシュガー。[2011-01-04]
ジェネリクスの可視境界

定義
def メソッド[T](implicit f: T => C) = {
  〜
}
def メソッド[T <% C] = {
  〜
}
 
例1
import scala.runtime.RichInt

def method[T](t:T)(implicit f: T=>RichInt) = {
  t.abs
}
import scala.runtime.RichInt

def method[T <% RichInt](t:T) = {
  t.abs
}
ここでの変数tは、RichIntのメソッドを呼ぶコーディングが出来る。
method(-123)		//暗黙変換であるPredef.intWrapper()によってInt→RichInt
method(new RichInt(-1))	//RichIntを直接指定
例2
class ZZZ(val n:Int) {
  def zzz() = "zzz" + n
}

implicit def int2zzz(n:Int) = {
  println("Int to ZZZ")
  new ZZZ(n)
}
暗黙変換メソッドがいつ呼ばれるのか確認してみた。

暗黙変換メソッドが呼ばれるのは、暗黙変換しないとそのメソッドが呼べない時。
(左記のt.zzz()の呼び出し時点)

tが直接そのインスタンスであれば、暗黙変換メソッドは呼ばれない。
(ZZZを受け取ってZZZを返す暗黙の関数は生成されている)
def method[T](t:T)(implicit f: T=>ZZZ) = {
  println("start")
  t.zzz()
  println("end")
}
def method[T <% ZZZ](t:T) = {
  println("start")
  t.zzz()
  println("end")
}
scala> method(123) //暗黙変換が呼ばれる例
start
Int to ZZZ
end

scala> method(new ZZZ(111)) //直接インスタンスを指定する例
start
end
例3
class ZZZ[N](val n:N) {
  def zzz() = "zzz" + n
}

implicit def int2zzz(n:Int) = {
  new ZZZ(n)
}
ZZZに対して型パラメーターを付けて汎用化してみた。
すると、直接インスタンスを指定した場合に
暗黙変換メソッドが見つからずにコンパイルエラーとなった。
まぁ言われてみれば(型パラメーターの値まで含めた)型が違うので、当然。
def method1[T](t:T)(implicit f: T=>ZZZ[T]) = {
  t.zzz()
}
def method2[T <% ZZZ[T]](t:T) = {
  t.zzz()
}
scala> method1(123) //暗黙変換が呼ばれる例
res18: java.lang.String = zzz123

scala> method1(new ZZZ(111)) //直接インスタンスを指定する例1
<console>:9: error: could not find implicit value for parameter f: (ZZZ[Int]) => ZZZ[ZZZ[Int]]
       method1(new ZZZ(111))
              ^
scala> method2(new ZZZ(111)) //直接インスタンスを指定する例2
<console>:9: error: could not find implicit value for evidence parameter of type (ZZZ[Int]) => ZZZ[ZZZ[Int]]
       method2(new ZZZ(111))
              ^
例4
def method[T](t:T)(implicit f: T=>List[String]) = {

  f(t)
}
def method[T <% List[String]](t:T) = {
  val f = implicitly[T=>List[String]]
  f(t)
}
明示的に関数を取得する例。
implicit def slist(n:Int) = List("s" + n)
method(111)
例5
import scala.runtime.RichInt

class Sample[T](val t:T)(implicit val f: T=>RichInt) {

}
import scala.runtime.RichInt

class Sample[T <% RichInt](val t:T) {
  val f = implicitly[T=>RichInt]
}
クラスで明示的に関数を取得する例。

Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま