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
selectorout ofprovide, , either chosen candidate or failed compile. - if guard
providerequiring receive implicitselector[l,t](in caseapplyselectort) 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