たのしい人生

Scala の implicit parameter のスコープと左辺値型推論で困った話

以下の記事は 2016 年 7 月 に Qiita に投稿した記事の移植です

pixivFANBOX にも投稿しています。ご支援頂ける場合はよろしくお願いいたします。

www.pixiv.net


Scala の特徴的言語仕様の一つであり、憎まれたり便利だったりする implicit の小ネタです。いろいろ検証過程を書いてるので、結論だけ知りたい人は最後を見てね。

便利な implicit parameter

implicit には何種類かあるのはご存知という前提で。implicit parameter 便利ですよね。

implicit val g = "Hello "

def hoge(name: String)(implicit greet: String) {
  println(greet + name)
}

hoge("Biacco") // 2番めのパラメタ群については implicit に渡される

ただ、この implicit が解決されるスコープと型推論が組み合わさるとややこしいことになるようです。

implicit parameter の解決スコープと型推論がうまく動かない例

今回ハマったのはこれ。

object Main extends App{
    import Hoge._
    val h = new Hoge
    h.hoge
}

class Hoge {
    def hoge(implicit s: String) { println(s) }
}

object Hoge {
    implicit val hs = "hoge!"
}

一見動きそう。でもコンパイルすると

error: could not find implicit value for parameter s: String

なるほどわからん

検証1: スコープを確認する

スコープに正しく入ってないのかな?と思って以下を試す。

object Main extends App{
    import Hoge._
    val h = new Hoge
    h.hoge(hs) // 明示的に hs を渡してみた
}

class Hoge {
    def hoge(implicit s: String) { println(s) }
}

object Hoge {
    implicit val hs = "hoge!"
}

動くんですよねこれが。コンパイル通ります。おっかしいな〜〜〜〜〜〜〜〜〜〜

検証2: ローカルスコープにおいてみる

というわけで、スコープに存在しないわけではないというのがわかった。じゃあ間違いなくアクセスできるということで試しにローカルスコープにおいてみる。

object Main extends App{
    import Hoge._
    
    implicit val hs = "hoge!"
    
    val h = new Hoge
    h.hoge
}

class Hoge {
    def hoge(implicit s: String) { println(s) }
}

object Hoge {
}

これもOK. この辺でかんべんしてくれという気持ちになり始める。

問題の定義: implicit な変数がスコープ内にいるにもかかわらず implicit parameter で解決されない

というわけで、現在の問題は

  • implicit な変数はスコープ内にいる
  • implicit parameter がそれを解決できない
  • implicit な変数はローカルスコープではなく import のスコープにいる

になります。一応 暗黙のパラメータ解決優先順位 なんかを見てみるものの、import した場合は Prefix なしの状態で参照できることとなっているが、 import Hoge._ しているのでそこも問題なさそうである。

解決法

まぁ、タイトルにもしているのでお気づきかと思いますが、この問題は implicit parameter と左辺値型推論が組み合わさると発生するようです。なので明示的に型注釈を与えてみます。

object Main extends App{
    import Hoge._
    
    val h = new Hoge
    h.hoge
}

class Hoge {
    def hoge(implicit s: String) { println(s) }
}

object Hoge {
    implicit val hs: String = "hoge!" // implicit な変数に型注釈をつける
}

と、コンパイルできるようになりました!出力もちゃんと hoge! になる。なんだったんだこれ...

解決した後に見つけたんですが OE さんがもう書いてた。死にたい。

Scala コミュニティでは定期的に話題になる問題のようです。きびしい。

Amazon.co.jpアソシエイト