Scalaの勉強をしていて下記のようなコードが出てきた
https://dwango.github.io/scala_text/test.html#fn_3
import org.scalatest._ class CalcSpec extends FlatSpec with DiagrammedAssertions { val calc = new Calc "sum関数" should "整数の配列を取得し、それらを足し合わせた整数を返すことができる" in { assert(calc.sum(Seq(1, 2, 3)) === 6) assert(calc.sum(Seq(0)) === 0) assert(calc.sum(Seq(-1, 1)) === 0) assert(calc.sum(Seq()) === 0) } }
文字列 should 文字列 in { } ってなんなのか?
とりあえずIDEでコードを書いて should の定義へジャンプしてみると...
def should(right: String)(implicit svsi: StringVerbStringInvocation): ResultOfStringPassedToVerb = { svsi(leftSideString, "should", right, pos) }
※ FlatSpec が FlatSpecLike を継承していて、
FlatSpec が ShouldVerb (とかいろいろ) を継承しているのでShouldVerbにアクセス出来ている
カリー化
まず、def should(right)(svsi): ResultOfStringPassedToVerb = {} というのはカリー化
https://sites.google.com/site/scalajp/home/documentation/scala-by-example/chapter5/section2
scala> def func(a: Int)(b: Int): Int = (a + 3) * b scala> func(3)(4) res1: Int = 24 scala> val f3 = func(3)(_) f3: Int => Int = <function1> scala> f3(5) res3: Int = 30
上記のように func(a: Int)(b: Int) みたいな感じで定義した関数は func(3)(4) のような形で呼び出すことができる
また、 val f3 = func(3)(_) とすると、1つめの引数を3としたうえで、2つめの引数だけを受け取る関数にすることができる
したがってf3(5) は func(3)(5) と同じ結果になる
shouldはこのように should(foo)(bar) のような形で呼び出すことができる関数らしい
ドットとカッコの省略
scalaではメソッド呼び出しの時の'.'を省略できるらしい (hoge.func() は hoge func()とかける)
引数が1つだけのメソッドを呼び出すときはカッコを省略できるらしいが、このときはかならずドットも省略しておかなければならないらしい (hoge func 3 はOKだけど hoge.func 3 はだめ)
引数が1つだけのメソッドを呼び出すときは() の代わりに {} をつかってもいいらしい
http://qiita.com/tag1216/items/d0c5970f840c9ff30925
scala> class Foo { | def func1(a: Int): Int = a * 3 | def func2(a: Int)(b: Int): Int = (a + 3) * b | } defined class Foo scala> val foo = new Foo() foo: Foo = $iwC$$iwC$Foo@4bdd6fe0 scala> foo.func1(2) res9: Int = 6 scala> foo func1(2) res10: Int = 6 scala> foo func1 2 res11: Int = 6 scala> foo.func1 2 <console>:1: error: ';' expected but integer literal found. foo.func1 2 ^ scala> foo.func1 { 4 } res0: Int = 12 scala> foo func1 { 4 } res1: Int = 12 scala> foo.func2(3)(4) res12: Int = 24 # func2ではこの手の省略はだめらしい scala> foo func2(3)(4) <console>:23: error: Int(3) does not take parameters foo func2(3)(4) ^ scala> foo func2(3) 4 <console>:1: error: ';' expected but integer literal found. foo func2(3) 4 ^ scala> foo func2 3 4 <console>:1: error: ';' expected but integer literal found. foo func2 3 3
暗黙の型変換
後ろのほうはあとで考えるとして、 文字列 should ... というのは "abc".should のドットを省略しているのだろう
でもStringにshouldなんてメソッドはない
scalaでは暗黙の型変換というものが利用できるらしい
https://dwango.github.io/scala_text/implicit.html
scala> class MyString(s: String) { | def twice(): String = s + s | } defined class MyString scala> implicit def convertToMyString(s: String): MyString = new MyString(s) warning: there were 1 feature warning(s); re-run with -feature for details convertToMyString: (s: String)MyString scala> "def".twice() res3: String = defdef
上記のようにimplicitをつけて関数を定義しておくと、引数の型のオブジェクトを必要に応じて変換してくれる
ここでは"def"という文字列をMyString型にこっそり変換してくれるのでString型でtwiceというメソッドがあるように見えるようになる
これと同様の変換がshouldの前の文字列に対しても行われているのでは?とおもって探したら下記が見つかった
implicit def convertToStringShouldWrapperForVerb(o: String)(implicit position: source.Position): StringShouldWrapperForVerb = new StringShouldWrapperForVerb { val leftSideString = o.trim val pos = position } }
Stringを受け取って、shouldが定義してあった StringShouldWrapperForVerb へ変換するimplicitな関数なので多分間違いないが、(implicit position: source.Position) が気になる
implicitな引数
引数にimplicitを付けておくと、その引数を渡さなくても予めimplicitをつけておいた変数をその引数としてつかってくれるらしい
scala> implicit val num: Int = 123 num: Int = 123 scala> def impargFunc(implicit a: Int): Int = a * 2 impargFunc: (implicit a: Int)Int scala> impargFunc(3) res4: Int = 6 scala> impargFunc() <console>:27: error: not enough arguments for method impargFunc: (implicit a: Int)Int. Unspecified value parameter a. impargFunc() ^ scala> impargFunc res6: Int = 246
implicitな引数を利用するときはカッコごと省略しなければならないらしい
先ほどの implicit def convertToStringShouldWrapperForVerb(o: String)(implicit position: source.Position): StringShouldWrapperForVerb も source.Position な変数がどこかでimplicitをつけて定義されているはずだ
implicitな引数に具体的な値を渡さなかった場合,コンパイラはimplicitが付いている値・関数から型の合うものを探し,渡してくれる。(探す範囲はその呼び出しのスコープ内からです。)
なので変数でなくてもいいらしい
ShouldVerb.scala からimportされている箇所をさがすとどうやら
がimplicitなPositionとして利用されるらしい
implicit def here: Position = macro PositionMacro.genPosition
内容まではよくわからないが一旦、ここで定義されているPositionが利用されるらしい、ということにしてこれについては切り上げよう
ここまでのまとめ
shouldの正体を追いかけて
def should(right: String)(implicit svsi: StringVerbStringInvocation): ResultOfStringPassedToVerb
はStringを暗黙の型変換でStringShouldWrapperForVerbに変換し、そのメソッドとして呼び出されているようだ、ということがわかった
うえのほうでほったらかしていたが、svsiもimplicitな引数なのでどこかでimplicitな値 or 関数が定義されていてそれが利用されるのだろう
ということで、
文字列 should 文字列 in { }
の2つめの文字列までについては
文字列.should(文字列) であり、この型は ResultOfStringPassedToVerbであることがわかった
ということで、次の in は多分 ResultOfStringPassedToVerbのメソッドなのだろう
これについて見ていく
in
inの定義にジャンプしてみたらResultOfStringPassedToVerbではなく、InAndIgnoreMethods というクラスで定義されていた
一瞬面食らったが、よくみたら暗黙の型変換が行われていた
protected implicit def convertToInAndIgnoreMethods(resultOfStringPassedToVerb: ResultOfStringPassedToVerb): InAndIgnoreMethods = new InAndIgnoreMethods(resultOfStringPassedToVerb)
ということで、改めてinの定義をみてみる
def in(testFun: => Any /* Assertion */)(implicit pos: source.Position): Unit = { registerTestToRun(verb.trim + " " + rest.trim, "in", List(), testFun _, pos) }
posについてはshouldの時と全く同じ
testFun の型は => Any となっている
これはjavaでいうところのSupplier(つまり引数をとらず、戻り値を返す関数)なのかなと思ったがどうも違うらしい
http://www.ne.jp/asahi/hishidama/home/tech/scala/def.html#h_call_by_name
scala> def func(f: => Any) = f func: (f: => Any)Any scala> func({ 123 }) res7: Any = 123 scala> func(222) res8: Any = 222 scala> func({ val a = 123; val b = "abc"; 333 }) res10: Any = 333
"名前渡しにすると、関数(関数リテラル)も渡せるし、普通の値も渡せるようになる。
名前渡しした関数は、実際に使われるまで評価(実行)されない。"
ということらしい
まとめ
長々と書いたが
文字列 should 文字列 in { }
は
文字列.should(文字列).in({})
だった
慣れればこういうのを見てもどれがメソッド呼び出しでどれが引数なのか、とかすっとわかるようになるのかしら