scala - Infinite recursion with Shapeless select[U] -
i had neat idea (well, that's debatable, let's had idea) making implicit dependency injection easier in scala. problem have if call methods require implicit dependency, must decorate calling method same dependency, way through until concrete dependency in scope. goal able encode trait requiring group of implicits @ time it's mixed in concrete class, go calling methods require implicits, defer definition implementor.
the obvious way kind of selftype la psuedo-scala:
object thingdoer { def getsomething(implicit foo: foo): int = ??? } trait mytrait { self: requires[foo , bar , bubba] => //this fails compile unless dothing takes implicit foo def dothing = thingdoer.getsomething }
after few valiant attempts implement trait and[a,b]
in order nice syntax, thought smarter start shapeless , see if anywhere that. landed on this:
import shapeless._, ops.hlist._ trait requires[l <: hlist] { def required: l implicit def provide[t]:t = required.select[t] } object thingdoer { def needsint(implicit i: int) = + 1 } trait mytrait { self: requires[int :: string :: hnil] => val foo = thingdoer.needsint } class myimpl extends mytrait requires[int :: string :: hnil] { def required = 10 :: "hello" :: hnil def showme = println(foo) }
i have say, pretty excited when compiled. but, turns out when instantiate myimpl
, infinite mutual recursion between myimpl.provide
, required.provide
.
the reason think it's due mistake i've made shapeless when step through, it's getting select[t]
, steps hlistops (makes sense, since hlistops has select[t]
method) , seems bounce call requires.provide
.
my first thought it's attempting implicit selector[l,t]
provide
, since provide
doesn't explicitly guard against that. but,
- the compiler should have realized wasn't going
selector
out ofprovide
, , either chosen candidate or failed compile. - if guard
provide
requiring receive implicitselector[l,t]
(in caseapply
selector
t
) doesn't compile anymore duediverging implicit expansion type shapeless.ops.hlist.selector[int :: string :: hnil]
, don't know how go addressing.
aside fact idea misguided begin with, i'm curious know how people typically go debugging these kinds of mysterious, nitty-gritty things. pointers?
when confused related implicits / type-level behaviour, tend find reify
technique useful:
scala> import scala.reflect.runtime.universe._ import scala.reflect.runtime.universe._ scala> val required: hlist = hnil required: shapeless.hlist = hnil scala> reify { implicit def provide[t]:t = required.select[t] } res3: reflect.runtime.universe.expr[unit] = expr[unit]({ implicit def provide[t]: t = hlist.hlistops($read.required).select[t](provide); () })
at point it's easy see what's gone wrong - compiler thinks provide
can provide arbitrary t
(because that's you've told it), calls provide
required selector[l, t]
. @ compile time resolves once, there no diverging implicit, no confusion @ compile time - @ run-time.
the diverging implicit expansion happens because compiler looks selector[int :: string :: hnil]
, thinks provide
give 1 if given selector[int :: string :: hnil, selector[int :: string :: hnil]]
, thinks provide
give 1 if given selector[int :: string :: hnil, selector[int :: string :: hnil, selector[int :: string :: hnil]]
, @ point realises infinite loop. where/how expecting selector
needs? think provide
misguided because it's general. try making call thingdoer.needsint
explicit int work first before trying make implicit.
this general approach work - i've written applications use di mechanism -though beware of quadratic compile times.
Comments
Post a Comment