ux00ff

ビールとプログラミングと

implicit parameter として引数と戻り値が同じ型の関数型を指定するとコンパイルが通らない

Mac OS Sierra / scala 2.12.1 にて。結論から言えば、Predef で定義されているものとコンフリクトするので、自前で利用する implicit parameter にはちゃんと固有の型をつけなさいって話であり。

タイトル通りの現象にひとしきり悩んだ

以下のような test.scala を用意する。

implicit val intToString = (a:Int) => s"string ${a}"

def lovelyIntAndString(implicit f: Int => String) = f(10)

println(lovelyIntAndString)

で、スクリプトモードで実行すると、想像通りの動作をするわけです。

$ scala test.scala
string 10

lovelyIntAndString メソッドが、implicit parameterとして、同じスコープに存在する intToString を拾って動作しています。

これが、メソッドのシグネチャInt => Intにすると、コンパイルが通りません。以下は test2.scala として作成したサンプル。

implicit val intToint = (a:Int) => a

def lovelyInt(implicit f: Int => Int) = f(10)

println(lovelyInt)

これを同様に実行すると、

$ scala test2.scala
/Users/ma2saka/Developments/test.scala:5: error: ambiguous implicit values:
 both method $conforms in object Predef of type [A]=> <:<[A,A]
 and value intToint of type => Int => Int
 match expected type Int => Int
println(lovelyInt)
        ^
one error found

コンパイルが通らない。ここではシグネチャInt => Int にしてるけど、String => String でも Fuga => Fuga でも同じです。

なぜだ

<:<とか=:=などについてはゆるよろ日記さんを参考にさせてもらいながら、

もとのコードを-Xprint:typer とかした時の抜粋が以下なんですが、

  private[this] val intToint: Int => Int = ((a: Int) => a);
  implicit <stable> <accessor> private def intToint: Int => Int = $anon.this.intToint;
   private def lovelyInt(implicit f: Int => Int): Int = f.apply(10);
   scala.Predef.println(lovelyInt)

おかしなところは見当たりません。

<:< は以下のようなシグネチャになっており、

  @implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
  sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
  private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
  implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

  @deprecated("use `implicitly[T <:< U]` or `identity` instead.", "2.11.0")
  def conforms[A]: A <:< A = $conforms[A]

よくみると、

sealed abstract class <:<[-From, +To] extends (From => To)

だから、

<:<(From => To) を継承している。

で、implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A] であるから、とある型Aに対して、 A <:< Aシグネチャを取る関数オブジェクトは常にimplicit で解決される。なるほどそういうことか。

解決策

固有の型を作ろう。そもそも (String => String) なんて広い型を implicit で使ってはいけなかったんや。

abstract class StringFunc extends (String => String)

implicit object stringFunc extends StringFunc {
  def apply(s : String) = {
    s"my ${s}"
  }
}

def lovelyName(implicit f: StringFunc) = f("hello world")

println(lovelyName)

しかし

暗黙にインポートされる Predef(A => A) にマッチする implicit オブジェクトが定義されているのはちょっとひどいと思ったりなどした。まあ動作が把握できたのでヨシ。