1

私には、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
}

その優れた構文を得るために実際に atrait and[A,B]を実装しようと勇敢に何度か試みた後、私は shapeless から始めて、それでどこにでも行けるかどうかを確認する方が賢明だと思いました。私はこのようなものに着陸しました:

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.provideRequired.provide

shapeless で犯した間違いが原因だと思う理由は、ステップスルーすると、それに到達してselect[T]から HListOps にステップインし (HListOps にはselect[T]メソッドがあるため、理にかなっています)、その後跳ね返るように見えるからです。への別の呼び出しにRequires.provide

私の最初の考えは、それを明示的に保護していないため、からSelector[L,T]暗黙的に取得しようとしているということでした。しかし、provideprovide

  1. コンパイラは、 を取得するつもりはないことを認識し、Selectorprovideの候補を選択するか、コンパイルに失敗する必要がありました。
  2. provide暗黙的に受け取ることを要求してガードすると (この場合、を取得するSelector[L,T]だけで済みます)、 が原因でコンパイルされなくなります。これは、アドレス指定の方法がよくわかりません。applySelectorTdiverging implicit expansion for type shapeless.ops.hlist.Selector[Int :: String :: HNil]

私の考えがそもそも間違っている可能性があるという事実は別として、人々が通常、この種の神秘的で核心を突いたものをどのようにデバッグしているのか知りたいです。ポインタはありますか?

4

1 に答える 1

2

暗黙的/型レベルの動作に関連する何かについて混乱するとき、私は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のを提供できると考えているため (それはあなたが指示したためです)、必要な を取得するために呼び出すだけです。コンパイル時にこれは 1 回だけ解決されるため、暗黙の発散やコンパイル時の混乱はなく、実行時にのみ解決されます。 TprovideSelector[L, T]

発散する暗黙の展開が発生するのは、コンパイラがaSelector[Int :: String :: HNil]を探すためです。必要なものをどこで/どのように入手することを期待していますか? あまりにも一般的すぎて誤解されていると思います。すべてを暗黙的にしようとする前に、明示的な int での呼び出しを最初に行ってみてください。provideSelector[Int :: String :: HNil, Selector[Int :: String :: HNil]]provideSelector[Int :: String :: HNil, Selector[Int :: String :: HNil, Selector[Int :: String :: HNil]]SelectorprovideThingDoer.needsInt

この一般的なアプローチは機能します - 私はこれを DI メカニズムとして使用するアプリケーションを作成しました - ただし、二次コンパイル時間には注意してください。

于 2015-07-02T13:28:39.217 に答える