难以理解无形代码

时间:2016-06-10 00:26:28

标签: scala shapeless

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

所以我从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 个答案:

答案 0 :(得分:2)

  1. 该程序的目的是比较同一案例类的两个实例。这是通过异构列表(HList s)完成的,它可以作为案例类的通用表示。无形提供了在案例类和通用HList表示之间进行转换的基础结构。请注意,在此示例中,差异不是递归计算的,即任何嵌套的case类实例都将以原子方式进行比较。
  2. 让我们假设Diff特征和伴侣对象是相当不言自明的。
  3. SimpleDelta特征定义了一个带有两个参数(DepFn2)的函数。签名是(R, R) => Out。此外,声明了对结果类型的约束:Out <: HList。这意味着,对于SimpleDelta的所有子类型,类型成员Out必须是HList的子类型。
  4. 对象SimpleDelta定义了类型Aux。您可以在网络上找到有关Aux模式的更多信息,例如here。简而言之,它充当SimpleDelta特征的别名,允许使用类型参数表达Out类型成员。
  5. 现在SimpleDelta对象定义了两个隐式方法hnilDeltahconsDelta。这些方法中的每一个都代表HListHNilHCons的可能构造之一。 HList A :: B :: HNil也可以理解为::(A, ::(B, HNil))HCons(A, HCons(B, HNil))。使用这两种方法,可以递归地解构和处理输入HList。根据这些方法的返回类型,编译器知道在遇到SimpleDelta[R]类型的隐式参数时隐式解析哪一个:当R被推断为HNil时,它将调用hnilDelta;如果某些类型RHead :: Tail已将HCons(Head, Tail)推断为Head(或Tail),则会调用hconsDelta
  6. hnilDelta方法确定两个空HList之间的差值(HNil为空HList)。
    1. 请注意,Out类型成员设置为HNil,这是HNil对象的类型。结果是HNil,因为没有区别。
    2. 方法的返回类型为Aux[HNil, HNil]。如上所述,这是SimpleDelta[HNil] { type Out = HNil }的别名。它告诉编译器在遇到类型为SimpleDelta[R]的隐式参数时调用此方法,并且R已被推断为HNil
  7. hconsDelta方法确定两个非空HList s之间的差值,即两个由HLists类型的头元素和尾部组成的H类型列表T
    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已针对某些类型H::T和{{1}推断为H }。
    4. 类型成员T设置为Out,这是产生差异的Diff[H] :: DT类型。
    5. HList方法确定两个头元素之间的差异,并将其预先设置为尾部列表之间的差异,尾部列表使用隐式参数apply计算(见上文)。
  8. tailDelta方法负责将apply类型的实例lr转换为其通用A表示形式,并调用{{1}在这些HList上计算差异。
    1. 类型SimpleDeltaHList的通用R表示形式。隐式参数HList表示该方法需要在AgenA: Generic.Aux[A, R]之间进行转换。
    2. 此外,该方法声明了一个隐式参数A。这是计算Rdelta: SimpleDelta[R]的两个通用HList表示之间的差异所必需的。
    3. 方法的返回类型为l。请注意,此处使用了依赖类型,因为r参数的实际delta.Out类型成员未知 - 它取决于实际类型Out及其delta表示{ {1}}。
  9. 编译A时会发生什么?

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