Scala の implicit conversions がよくわかんなかったからいろいろやってようやくわかった気がする

アホなので implicit conversions みたいな魔術っぽいことされると「えっなになに」「なにが起きてるの」ってなってしまって混乱する。いままでの「なんとなくこんな感じのアレだよね」みたいなやつとしては、たとえば

  • String を wrap するような SugoiString クラスを準備して
  • SugoiString#sugoiMethod みたいなメソッドを定義しておいて
  • implicit def stringToSugoiString(s:String) => new SugoiString(s) みたいな感じでで String を SugoiString に変換するメソッド書いておく

そうすると、"nyan".sugoiMethod みたいにすると勝手に stringToSugoiString("nyan").sugoiMethod が呼び出される

くらいの理解だったんだけど、この「勝手に」の部分の挙動がよくわかってなかった。で、下みたいな例を書いてみてようやくわかった感じがした。

class NyanString(string:String){
  def shout = {
    println(string.toUpperCase + " nyaaaaaaan!")
  }
}
implicit def convertStringToNyanString(s: String) = new NyanString(s)

class WanString(string:String){
  def shout = {
    println(string.toUpperCase + " wan! wan!")
  }
}
implicit def convertStringToWanString(s: String) = new WanString(s)


"nyan".shout

要するに shout メソッドもってるクラスが二つあって、なおかつ String からそれらへの変換メソッドが implicit def で 定義されてるときに "nyan".shout するとどうなるのかな、というのを確かめるスクリプトだ。

これを実行したみた結果、以下のとおりコンパイルが通らない

  ✘  scala nyan.scala
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8
/path/to/nyan.scala:16: error: type mismatch;
 found   : String("nyan")
 required: ?{def shout: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method convertStringToNyanString of type (s: String)this.NyanString
 and method convertStringToWanString of type (s: String)this.WanString
 are possible conversion functions from String("nyan") to ?{def shout: ?}
"nyan".shout
^
/path/to/nyan.scala:16: error: value shout is not a member of String
"nyan".shout
       ^
two errors found

Note として「ambiguous なせいで implicit conversions が行われなかったよ〜」と書かれている。なるほど〜。つまり、implicit conversionsの挙動は、コンパイル時に以下のような感じで行われる。

  • instanceOfA.nyanみたいなのがソースコード中にあるんだけど「Aにnyanなんて定義されてねーし」となったら、
  • コンパイラが以下を満たすメソッドを探してくる
    1. A を引数にとり "nyan をメソッドに持つ型" を返す
    2. impilicit def で宣言されている
  • このメソッドが 1 個だけしかない場合は implicit conversion を行う!
  • 2 個以上みつかった場合はエラーとなる!

ということですね。なんか「暗黙すぎて意味わかんないよ」みたいになってたことがちょっとはっきりした。あとはなんか最近は implicit class ってのもあるらしいのでそれを明日調べることができたら調べる。

こういう既存のクラスを拡張するみたいなやつ、 Ruby だったらクラスオープンしてそこにばっこんって定義しちゃう感じだとおもうし Perl も既存の package に新しくメソッド生やしちゃうみたいな感じが一般的なやりかただとおもうけど、このやりかたはこのやりかたでそういう拡張を実現する方法として面白いなーって感じがする。

あくまでメソッドは String ではなくて SugoiString クラスに生えてるので、コンパイル後にはSugoiString#sugoiMethod が呼ばれてるんだけど、ソースコード上の見かけは String#sugoiMethod が呼ばれてるみたいに見えるのが、なんかちょっと C で言うところのプリプロセッサ、それのめっちゃすごいやつバージョンみたいな挙動だなーって思っておもしろいなーって思った。