S-JIS[2010-12-12/2015-09-22] 変更履歴
Scalaのメソッドの定義方法に関するメモ。
|
|
メソッドはdefで定義する。[2010-10-23]
(とりあえず、defで定義するものは「メソッド」と呼ぶことにする)
メソッドはclassやobjectやtrait内に定義する。
メソッド内に新しいメソッド(関数)を定義することも出来る。
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式でなく)再帰呼び出しを使った方が関数型プログラミングっぽいかもしれない。 |
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”なくせに、コンストラクターより早く動く(笑)
Scalaには、暗黙の型変換(implicit conversion)と呼ばれる仕組みがある。[2010-12-26]
implicit def 関数名(引数:変換元クラス) : 変換後クラス = 〜
こういった関数(メソッド)を定義してインポートしておくと、変換元クラスのインスタンスに対して変換後クラスのメソッドが呼べるようになる。
Intの値でRichIntのメソッドが呼べたり、配列(Array)でArrayOpsのメソッドが呼べたりするのは、自動的にインポートされるPredefの中で暗黙変換のメソッドが定義されているから。
例えば「val n = -12; n.abs
」という、Intの変数nに対するRichIntのメソッドabsの呼び出しは、以下のようになっている。
implicit def intWrapper(x: Int) = new runtime.RichInt(x)
n.abs
」という呼び出しは、「intWrapper(n).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
通常 | 無効化 | 上書き | 備考 |
---|---|---|---|
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 |
scala> new C().f res4: java.lang.String = zzzaaa |
例えば自分のクラスからStringへ変換するメソッドは、自分のクラス内にtoString()メソッドを作ればいい。
逆にStringから自分のクラスへ変換したい場合は、Stringに対して「to自クラス()」というメソッドを用意したいところだがStringクラスは修正できない。
そこで暗黙変換を定義してインポートしてやれば、「to自クラス()」という呼び出しが出来るようになる。
(Javaではこういった事が出来ないので、valueOf(String)といったstaticメソッドを自クラス内に用意するような方法を採る)
この(自分で修正することのできない)既存クラスに後からメソッドを追加する(ように見せる)為の実装方法を「pimp my library」パターンという。
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]
//StringにisUpper()メソッドを追加する 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
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は引数リストの先頭の変数にしか付けられないので、引数リストを複数持つ形で使われることが多いのではないかと思う。
「暗黙の引数である」という指定がされた引数は、呼び出し側で省略することが出来る。
その場合、その引数の型に応じた暗黙の値がセットされる。暗黙の値が見つからなければコンパイルエラーになる。
暗黙の値は、valやobject等の前に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]
)
暗黙のパラメーター(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(可視境界)は、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] } |
クラスで明示的に関数を取得する例。 |