PS

shapeless.Lazy

shapelessのLazyを攻略する試み。*1

実装

implicitlyのためにmacroで実装される。quasiquoteを使うと

trait Lazy[T] {
    val value: T
}

object Lazy {
    implicit def mkLazy[T]: Lazy[T] = macro mkLazyImpl[T]

    def mkLazyImpl[T](c: Context)(t: c.WeakTypeTag[T]): c.Tree = {
        import c.universe._

        val recName = TermName(c.freshName)

        q"""
        new Lazy[${t.tpe}] {
            implicit val $recName: Lazy[${t.tpe}] = this
            override lazy val value: ${t.tpe} = implicitly[${t.tpe}]
        }
        """
    }
}

のような感じになっている。*2

$recNameがミソで、implicitlyのargumentの探索の過程で、再びLazy[T]が必要になれば、 $recNameが採用されて探索はストップする。*3

trait Undefined
implicit def mkUndefined(implicit x: Lazy[Undefined]): Undefined = new Undefined {}

def myImplicitly[T](implicit x: T): T = x

myImplicitly[Undefined]

最終的に、$recNamexに渡されて探索はストップする。

どういうわけか、標準のimplicitlyを使うとdivergingしてしまう。

参考文献

*1:HaskellではUndecidableInstanceというのが近いらしい

*2:なぜかfresh nameが必要

*3:言語仕様のどこにあるのか分からず