难以理解的无形代码



我正在尝试学习无形,但是我发现无形代码真的很难理解。

所以我从YouTube上的一次演讲中得到了这个代码示例。

https://www.youtube.com/watch?v=JKaCCYZYBWo

谁能向我解释发生了什么(一步一步)。我觉得最困难的是一切都是隐式的,所以很难跟踪代码....

另外,请指出一些可以帮助我理解此类代码的资源。每次我遇到包含如此多隐式的代码时,我都觉得我什至不了解 Scala。

import shapeless._
sealed trait Diff[A]
final case class Identical[A](value: A) extends Diff[A]
final case class Different[A](left : A, right : A) extends Diff[A]
object Diff {
def apply[A](left : A, right : A) : Diff[A] = {
if (left == right) Identical(left)
else Different(left, right)
}
}
trait SimpleDelta[R <: HList] extends DepFn2[R, R] {
type Out <: HList
}
object SimpleDelta {
type Aux[I <: HList, O <: HList] = SimpleDelta[I]{type Out = O}
implicit def hnilDelta: Aux[HNil, HNil] = new SimpleDelta[HNil] {
type Out = HNil
def apply(l : HNil, r: HNil) : Out = HNil
}
implicit def hconsDelta[H, T <: HList, DT <: HList](implicit tailDelta: Aux[T, DT])
: Aux[H::T, Diff[H] :: DT] = new SimpleDelta[H :: T] {
type Out = Diff[H] :: DT
def apply(l : H :: T, r: H :: T) : Out = 
Diff(l.head, r.head) :: tailDelta(l.tail, r.tail)
}
def apply[A, R <: HList](l : A, r: A)
(implicit genA: Generic.Aux[A, R], delta: SimpleDelta[R]) : delta.Out = 
delta(genA.to(l), genA.to(r))
}
case class Address(number: Int, street: String, city: String)
case class Character(name: String, age: Int, address: Address)
val homer = Character("Homer Simpson", 42, Address(742, "Evergreen Terrace", "SpringField"))
val ned = Character("Ned Flanders", 42, Address(744, "Evergreen Terrace", "SpringField"))
SimpleDelta(homer, ned)
  1. 该程序的目的是比较同一案例类的两个实例。这是通过异构列表(HLists)完成的,异构列表可以作为案例类的泛型表示。Shapeless 提供了在案例类及其泛型HList表示形式之间进行转换的基础结构。请注意,在此示例中,差值不是递归计算的,即任何嵌套的案例类实例都将以原子方式进行比较。
  2. 让我们假设Diff特征和伴随对象是不言自明的。
  3. SimpleDelta特征定义了一个具有两个参数(DepFn2)的函数。签名(R, R) => Out。此外,还声明了对结果类型的约束:Out <: HList。这意味着对于SimpleDelta的所有子类型,类型成员Out必须是HList的子类型。
  4. 对象SimpleDelta定义类型Aux。您可以在网络上找到有关 AUX 模式的更多信息,例如 这里.简而言之,它充当SimpleDelta特征的别名,允许使用类型参数表示Out类型成员。
  5. 现在SimpleDelta对象定义了两个隐式方法hnilDeltahconsDelta。这些方法中的每一个都代表了HListHNilHCons的可能构造之一。HListA :: B :: HNil也可以读作::(A, ::(B, HNil))HCons(A, HCons(B, HNil))。使用这两种方法,可以递归解构和处理输入HLists。基于这些方法的返回类型,编译器知道在遇到SimpleDelta[R]类型的隐式参数时要隐式解析哪一个: 当R被推断为HNil时,它将调用hnilDelta;当R被推断为某些类型的HeadTailHead :: Tail(或HCons(Head, Tail))时,它将调用hconsDelta
  6. hnilDelta方法确定两个空HLists 之间的增量(HNil是空HList)。
    1. 请注意,Out类型成员设置为HNil,这是HNil对象的类型。结果是HNil,因为没有区别。
    2. 该方法的返回类型为Aux[HNil, HNil]。如上所述,这是SimpleDelta[HNil] { type Out = HNil }的别名。它告诉编译器在遇到SimpleDelta[R]类型的隐式参数时调用此方法,并且R已被推断为HNil
  7. hconsDelta方法确定两个非空HLists.之间的增量,即由类型为H的头元素和类型T的尾表组成的两个HLists
    1. 类型参数DT表示表示尾部列表增量的HList子类型。
    2. 请注意隐式参数tailDelta :Aux[T, DT]。它传达了该方法必须能够确定两个列表lr的尾部之间的增量:tailDelta(l.tail, r.tail)
    3. 该方法的返回类型为Aux[H::T, Diff[H] :: DT]。如上所述,这是SimpleDelta[H::T] { type Out = Diff[H] :: DT }的别名。它告诉编译器在遇到类型为SimpleDelta[R]的隐式参数时调用此方法,并且R已被推断为对某些类型的HTH::T
    4. 类型成员Out设置为Diff[H] :: DT,这是结果差异的HList类型。
    5. apply方法确定两个头元素之间的差异,并将其附加到尾列表之间的差异之前,这是使用隐式参数tailDelta计算的(见上文)。
  8. apply方法负责将A类型的实例lr转换为其泛型HList表示形式,并调用这些HList上的SimpleDelta来计算差值。
    1. R类型是A的泛型HList表示形式。隐式参数genA: Generic.Aux[A, R]表示该方法需要在AR之间进行转换。
    2. 此外,该方法声明一个隐式参数delta: SimpleDelta[R]。这是计算lr的两个泛型HList表示之间的差异所必需的。
    3. 该方法的返回类型为delta.Out。请注意,此处使用依赖类型,因为delta参数的实际Out类型成员未知 – 它取决于实际的类型A及其HList表示R

编译SimpleDelta(homer, ned)时会发生什么?

  1. 类型化调用SimpleDelta.apply[Character, R](homer, ned)
  2. 编译器尝试查找隐式参数的匹配值genA: Generic.Aux[Character, R]delta: SimpleDelta[R]
  3. 无形状为案例类A提供了隐式Generic.Aux[A, R]。这种机制相当复杂,超出了这个问题的范围,但相关的是Character类型的泛型HList表示是String :: Int :: Address :: HNil的,因此编译器可以推断出genA的类型Generic.Aux[Character, String :: Int :: Address :: HNil]。这反过来意味着类型变量R可以推断为String :: Int :: Address :: HNil
  4. 现在类型R是已知的,编译器将尝试解析参数delta: SimpleDelta[R]的隐式值,即SimpleDelta[String :: Int :: Address :: HNil].当前作用域不包含任何具有此返回类型的隐式方法或具有此类型的值,但编译器还将查阅参数类型SimpleDelta的隐式作用域,其中包括配套对象SimpleDelta(请参阅 Scala 文档中的隐式参数部分)。
  5. 编译器在SimpleDelta对象中搜索提供SimpleDelta[String :: Int :: Address :: HNil]类型的对象的值或方法。唯一的匹配方法是hconsDelta,因此编译器将参数值delta连接到此方法的调用。请注意,HList分解为头和尾:hconsDelta[String, Int :: Address :: HNil, DT](implicit tailDelta: Aux[Int :: Address :: HNil, DT])
  6. 为了填充隐式tailDelta参数,编译器将继续查找匹配的隐式方法。这样,HList被递归解构,直到只剩下HNil并调用hnilDelta
  7. 在"向上"时,DT类型变量将被推断为Diff[Int] :: Diff[Address] :: HNil,返回类型hconsDeltaSimpleDelta[String :: Int :: Address :: HNil] {type Out = Diff[String] :: Diff[Int] :: Diff[Address] :: HNil]}。因此,apply的返回类型推断为Diff[String] :: Diff[Int] :: Diff[Address] :: HNil

最新更新