Бесконечная рекурсия с 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 явно не защищает от этого. Но,

  1. Компилятор должен был понять, что он не получит Selector снаружи provideи либо выбрал другого кандидата, либо не смог скомпилировать.
  2. Если я охраняю 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, хотя остерегайтесь квадратичного времени компиляции.

Другие вопросы по тегам