暗黙の解決がどのように機能するか、特にすべての暗黙のパラメーターに対して暗黙が検索される型の推論と順序 (存在する場合) に関して、良い情報源はありますか?
最初に、もちろん私は Scala 言語仕様を読んでいるので、コンパイラーがどこで暗黙を検索するかなどの基本を理解しています (またはそう思う)。しかし、再帰的暗黙のように、もう少し高度なことをしようとすると、常に超自然的な問題に遭遇し、それらを扱うことは実験的な魔法を練習しているように思えます. コンパニオン オブジェクトにすべての暗黙オブジェクトをまとめて保持し、競合に関するすべての困難な問題を排除したとしても、仕様に従ってセマンティクスを変更するべきではないコードのわずかな変更によって、コンパイルする場合とコンパイルしない場合に違いが生じることがあります。
Companion
コンパイラが暗黙的な for を見つけることができなかったが、呼び出しの前に導入import Companion._
するとコンパイルが行われ、オブジェクト Companion には Companion インスタンスを返す暗黙的な定義しかなかったというケースを覚えています。
コードが次のように構成されている場合がありました。
object ADT {
class Evidence;
object Evidence {
class ParticularEvidence extends Evidence
object ParticularEvidence {
implicit def ... :ParticularEvidence
}
}
...
def f(...)(implicit e:ParticularEvidence) = ...
...
f(...)
コンパイルしませんでしたが、暗黙の定義 (ParticularEvidence の唯一のもの) を ParticularEvidence から親 Evidence オブジェクトに移動すると、それらが表示されました。
いくつかの試行錯誤の後、解決ロジックがかなり制限されており、このクラスの構成要素がたとえば次のようになっていることがわかりました。
implicit def f[X, Y, Z](x :X)(implicit e1:Evidence1[X, Y], implicit e2:Evidence[Y, Z]) :Y
...
val x :X = ...
val z:Z = x
証拠クラスが型パラメーターに対して不変である場合、そのような暗黙の値が存在する型 Z が 1 つだけ存在する場合でも、ほとんどコンパイルされません。しかし、次のことが問題になる理由がわかりません。
/** Stub for the purpose of this example **/
sealed abstract class ||[+A,+B]
case class LeftVariant[+A](left :A) extends ||[A, Nothing]
case class RightVariant[+B](right :B) extends ||[Nothing, B]
object SupportingEvidence {
/** C is an Atom and U==C or U is a type union explicitly containg C */
sealed class ComponentOf[C, U] protected[SupportingEvidence]() extends SupportingEvidence
final class Atom[T] protected[SupportingEvidence]() extends ComponentOf[T, T]
implicit def atom[T](implicit ev :Not[T <:< ||[_,_]]) = a.asInstanceOf[Atom[T]]
implicit def leftComponent[A, B, C](implicit ev: C ComponentOf A): C ComponentOf (A || B) =
ev.asInstanceOf[ComponentOf[C, A || B]]
implicit def rightComponent[A, B, C](implicit ev: C ComponentOf B, but: Not[C ComponentOf A]): C ComponentOf (A || B) =
ev.asInstanceOf[ComponentOf[C, A || B]]
private[this] val a = new Atom[Nothing]
type Something[X] = Not[X<:<Nothing]
/** T = U - C, calculated as follows:
* U = A || B => A - C || B - C
* U <: C => Nothing
* else => U
*/
final class Minus[U, C, T] protected[SupportingEvidence]() extends SupportingEvidence
object Minus {
implicit def nothing[U, C](implicit atom :Atom[U], conforms :U ConformsTo C) :Minus[U, C, Nothing] =
certify[U, C, Nothing]
implicit def self[U, C](implicit atom :Atom[U], diff :Not[U ConformsTo C]) :Minus[U, C, U] =
certify[U, C, U]
implicit def left[A, B, C, T](implicit left :Minus[A, C, T],
leftSomething :Not[C ConformsTo A],
rightNothing :C ConformsTo B) :Minus[A || B, C, T] =
certify[A || B, C, T]
implicit def right[A, B, C, T](implicit leftNothing :C ConformsTo A,
right :Minus[B, C, T]) :Minus[A || B, C, T] =
certify[A || B, C, T]
implicit def union[A, B, C, L, R](implicit atom :Atom[C],
leftSomething :Not[C ConformsTo A],
rightSomething :Not[C ConformsTo B],
left :Minus[A, C, L],
right :Minus[B, C, R]) :Minus[A || B, C, L || R] =
certify[A || B, C, L || R]
private[this] def certify[U, C, T] = m.asInstanceOf[Minus[U, C, T]]
}
private[this] val m = new Minus[Nothing, Nothing, Nothing]
}
final class ConformsTo[-X, +Y] protected[ADT] (protected[ADT] val cast :X=>Y) //extends Conversion[X, Y]
object ConformsTo {
import SupportingEvidence._
private def apply[X, Y](fun :X=>Y) :ConformsTo[X, Y] = new ConformsTo(fun)
implicit def directlyConformsTo[X, Y](implicit ev :X <:< Y) :ConformsTo[X, Y] =
ConformsTo(ev.apply _)
implicit def conformsToLeft[X, A, B](implicit atom :Atom[X],
conform :ConformsTo[X, A],
only :Not[ConformsTo[X, B]]) :ConformsTo[X, A || B] =
ConformsTo((x :X) => LeftVariant(conform.cast(x)))
implicit def conformsToRight[X, A, B](implicit atom :Atom[X],
conform :ConformsTo[X, B],
only :Not[ConformsTo[X, A]]) :ConformsTo[X, A || B] =
ConformsTo((x :X) => RightVariant(conform.cast(x)))
implicit def conformsToBoth[X, A, B](implicit atom :Atom[X],
left :ConformsTo[X, A],
right :ConformsTo[X, B]) :ConformsTo[X, A || B] =
ConformsTo((x :X) => LeftVariant(left.cast(x)))
implicit def alternativesConform[A, B, Y](implicit left :ConformsTo[A, Y], right :ConformsTo[B, Y],
nonDirect :Not[(A || B) <:< Y]) :ConformsTo[A || B, Y] =
ConformsTo((x :A || B) => x match {
case LeftVariant(l) => left.cast(l)
case RightVariant(r) => right.cast(r)
})
}
}
/** Implicit value for Not[T] exists <=> there's no implicit value for T in scope */
final class Not[+T](override val toString :String)
object Not {
private[this] val nice = new Not[Nothing]("default")
private[this] val mean = new Not[Nothing]("conflict")
implicit def conflict[T](implicit ev :T) :Not[T] = mean
implicit def default[T] :Not[T] = nice
}
//test Minus
class SA
class SB
implicitly[Minus[SA || SB, Nothing, SA || SB]] //unhelpful divergent expansion error
ここでは、ボックス化されたタイプの共用体を試しています (||[A, B] は基本的に、構造の透明な平坦化と非表示の左/右オプションを備えた美化された [Eir] です)。Minus は、実装したい型演算の一部です。最後の行で、SA||SB - Nothing = SA||SB という証拠をコンパイラに提供するよう要求しています。より一般的には、C が A のすべての値を含み、B の値を含まない最小の型である場合にのみ、暗黙的なマイナス [A,B,C] が存在すると予想します。さらに、C がは不明であり、A、B からのみであるため、暗黙の証拠を実装して、任意の型共用体を正規化された形式に自動変換できるようにすることができます。最終的には、正規化された値を提供する暗黙的なメソッドの組み合わせを見つけて、次のようなものを記述できるようにしたいと考えています。
def normalize[A,B,N](v :A || B)(implicit :Normalized[A || B, N]) :N
val a :Int || java.sql.Date || Int || String || java.lang.Date = ...
val n = normalize(a) //n is of type Int || java.lang.Date || String
したがって、コンパイラは、暗黙的なメソッドによって適用される一連の規則を利用して、他の部分によって支配されている部分を取り除くことによって共用体型を単純化できるはずです。そのためには、型の集合減算を実装できる必要があります。
私はおそらくこれで敵の領域にいる可能性が高く、これに応じてコンパイラーの変更がプログラム全体を壊す可能性があることを知っていますが、私は主にパズルと概念の証明としてそれを扱います. 何が可能かを理解したら、何が合理的かを考え始めます。
したがって、特定の質問は次のとおりです。上記のコードの何が正確に間違っているのか、修正できるかどうかを知っている人はいますか?