Бесконечная рекурсия с Shapeless select[U]
У меня была отличная идея (ну, это спорный вопрос, но, скажем, у меня была идея) для создания инъекции неявную зависимость проще в Scala. У меня проблема в том, что если вы вызываете какие-либо методы, требующие неявной зависимости, вы также должны декорировать вызывающий метод с одной и той же зависимостью, до тех пор, пока эта конкретная зависимость, наконец, не окажется в области видимости. Моя цель состояла в том, чтобы иметь возможность кодировать черту как требующую группу имплицитов в то время, когда она смешана с конкретным классом, чтобы можно было вызывать методы, которые требуют имплицитов, но переносить их определение для разработчика.
Очевидный способ сделать это с помощью некоторого автотипа а-ля псевдо-скала:
object ThingDoer {
def getSomething(implicit foo: Foo): Int = ???
}
trait MyTrait { self: Requires[Foo and Bar and Bubba] =>
//this normally fails to compile unless doThing takes an implicit Foo
def doThing = ThingDoer.getSomething
}
После нескольких доблестных попыток trait and[A,B]
чтобы получить этот хороший синтаксис, я подумал, что будет разумнее начать с бесформенного и посмотреть, смогу ли я хоть что-нибудь получить с этим. Я приземлился на что-то вроде этого:
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) = i + 1
}
trait MyTrait { self: Requires[Int :: String :: HNil] =>
val foo = ThingDoer.needsInt
}
class MyImpl extends MyTrait with Requires[Int :: String :: HNil] {
def required = 10 :: "Hello" :: HNil
def showMe = println(foo)
}
Я должен сказать, я был очень взволнован, когда это на самом деле скомпилировано. Но оказывается, что когда вы на самом деле MyImpl
Вы получаете бесконечную взаимную рекурсию между MyImpl.provide
а также Required.provide
,
Причина, по которой я думаю, что это из-за какой-то ошибки, которую я совершил с бесформенным, заключается в том, что когда я прохожу, select[T]
и затем вступает в HListOps (имеет смысл, так как HListOps это то, что имеет select[T]
метод), а затем, кажется, приходит в норму в другой вызов Requires.provide
,
Моя первая мысль была о том, что он пытается получить неявное Selector[L,T]
от provide
, поскольку provide
явно не защищает от этого. Но,
- Компилятор должен был понять, что он не получит
Selector
снаружиprovide
и либо выбрал другого кандидата, либо не смог скомпилировать. - Если я охраняю
provide
требуя, чтобы он получил неявноеSelector[L,T]
(в этом случае я мог бы простоapply
Selector
чтобы получитьT
) то он больше не компилируется из-заdiverging implicit expansion for type shapeless.ops.hlist.Selector[Int :: String :: HNil]
, который я действительно не знаю, как идти о решении.
Помимо того факта, что моя идея, вероятно, неверна с самого начала, мне любопытно узнать, как люди обычно занимаются отладкой таких таинственных, мельчайших вещей. Есть указатели?
1 ответ
Когда я запутываюсь из-за чего-то, связанного с имплицитами / поведением на уровне типов, я склонен находить reify
Техника полезна:
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);
()
})
На данный момент легко увидеть, что пошло не так - компилятор думает provide
может предоставить любой произвольный T
(потому что это то, что вы сказали), так что это просто вызывает provide
чтобы получить необходимое Selector[L, T]
, Во время компиляции это разрешается только один раз, так что нет неявного расхождения, нет путаницы во время компиляции - только во время выполнения.
Различающееся неявное расширение происходит потому, что компилятор ищет Selector[Int :: String :: HNil]
думает provide
может дать ему один, если дан Selector[Int :: String :: HNil, Selector[Int :: String :: HNil]]
думает provide
может дать ему один, если дан Selector[Int :: String :: HNil, Selector[Int :: String :: HNil, Selector[Int :: String :: HNil]]
и в какой-то момент он понимает, что это бесконечный цикл. Где / как вы ожидаете получить Selector
это нужно? Я думаю твой provide
вводит в заблуждение, потому что это слишком общее. Попробуйте позвонить ThingDoer.needsInt
с явным int сначала работает, прежде чем пытаться сделать все это неявным.
Этот общий подход работает - я написал приложения, которые используют его как механизм DI, хотя остерегайтесь квадратичного времени компиляции.