难以理解无形代码
Hard to understand Shapeless code
我正在努力学习shapeless,但是我发现Shapeless代码真的很难理解。
所以我从 youtube 上的一次演讲中得到了这个代码示例。
https://www.youtube.com/watch?v=JKaCCYZYBWo
任何人都可以向我解释发生了什么(一步一步)。我发现最困难的是一切都是隐式的,所以很难追踪代码....
另外,请给我指出一些可以帮助我理解这样的代码的资源。每次遇到那么多implicit的代码都觉得自己连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)
- 该程序的目的是比较同一案例的两个实例 class。这是通过异构列表 (
HList
s) 完成的,它可以作为 case classes 的通用表示。 shapeless 提供了在 case class 和它的通用 HList
表示之间进行转换的基础设施。请注意,在此示例中,差异不是递归计算的,即任何嵌套案例 class 实例将被自动比较。
- 让我们假设
Diff
特征和伴随对象是不言自明的。
SimpleDelta
特征定义了一个有两个参数的函数 (DepFn2
)。签名是(R, R) => Out
。此外,声明了对结果类型的约束:Out <: HList
。这意味着对于 SimpleDelta
的所有子类型,类型成员 Out
必须是 HList
. 的子类型
- 对象
SimpleDelta
定义类型 Aux
。您可以在网上找到有关 Aux 模式的更多信息,例如here。简而言之,它充当 SimpleDelta
特征的别名,允许使用类型参数表达 Out
类型成员。
- 现在
SimpleDelta
对象定义了两个隐式方法 hnilDelta
和 hconsDelta
。这些方法中的每一个都代表 HList
、HNil
和 HCons
的可能构造函数之一。 HList
A :: B :: HNil
也可以读作 ::(A, ::(B, HNil))
或 HCons(A, HCons(B, HNil))
。使用这两种方法,可以对输入HList
s进行递归解构和处理。基于这些方法的return类型,编译器在遇到SimpleDelta[R]
类型的隐式参数时知道要隐式解析哪个:当R
被推断为HNil
, 它将调用 hnilDelta
;当 R
对于某些类型 Head
和 Tail
被推断为 Head :: Tail
(或 HCons(Head, Tail)
)时,它将调用 hconsDelta
.
hnilDelta
方法确定两个空 HList
之间的增量(HNil
是空 HList
)。
- 注意
Out
类型成员设置为HNil
,这是HNil
对象的类型。结果是HNil
,因为没有区别。
- 方法的return类型是
Aux[HNil, HNil]
。如上所述,这是 SimpleDelta[HNil] { type Out = HNil }
的别名。它告诉编译器在遇到 SimpleDelta[R]
类型的隐式参数时调用此方法,并且 R
已被推断为 HNil
.
hconsDelta
方法确定两个非空 HList
之间的增量,即两个 HLists
由 H
类型的头元素组成, T
类型的尾列表。
- 类型参数
DT
表示HList
子类型,表示尾列表的delta。
- 注意隐式参数
tailDelta :Aux[T, DT]
。它表示该方法必须能够确定两个列表 l
和 r
尾部之间的增量:tailDelta(l.tail, r.tail)
.
- 方法的return类型是
Aux[H::T, Diff[H] :: DT]
。如上所述,这是 SimpleDelta[H::T] { type Out = Diff[H] :: DT }
的别名。它告诉编译器在遇到 SimpleDelta[R]
类型的隐式参数时调用此方法,对于某些类型 H
和 T
,R
已被推断为 H::T
].
- 类型成员
Out
设置为 Diff[H] :: DT
,这是结果差异的 HList
类型。
apply
方法确定两个头元素之间的差异并将其添加到尾列表之间的差异,这是使用隐式参数 tailDelta
计算得出的(见上文)。
apply
方法负责将类型 A
的实例 l
和 r
转换为它们的通用 HList
表示,并调用 SimpleDelta
在这些 HList
上计算差异。
- 类型
R
是 A
的通用 HList
表示。隐式参数genA: Generic.Aux[A, R]
表示该方法需要在A
和R
之间进行转换。
- 此外,该方法声明了一个隐式参数
delta: SimpleDelta[R]
。这是计算 l
和 r
的两个通用 HList
表示之间的差异所必需的。
- 方法的return类型是
delta.Out
。请注意,此处使用了依赖类型,因为 delta
参数的实际 Out
类型成员未知 – 它取决于实际类型 A
及其 HList
表示 R
.
编译SimpleDelta(homer, ned)
时会发生什么?
- 键入的调用是
SimpleDelta.apply[Character, R](homer, ned)
。
- 编译器尝试为隐式参数
genA: Generic.Aux[Character, R]
和 delta: SimpleDelta[R]
. 寻找匹配值
- shapeless 为 case classes
A
提供隐式 Generic.Aux[A, R]
。这种机制相当复杂,超出了这个问题的范围,但相关的是类型 Character
的通用 HList
表示是 String :: Int :: Address :: HNil
,因此编译器可以推断类型genA
到 Generic.Aux[Character, String :: Int :: Address :: HNil]
。这反过来意味着类型变量 R
可以推断为 String :: Int :: Address :: HNil
.
- 既然类型
R
已知,编译器将尝试解析参数 delta: SimpleDelta[R]
的隐式值,即 SimpleDelta[String :: Int :: Address :: HNil]
。当前作用域不包含具有此 return 类型的任何隐式方法或具有此类型的值,但编译器还将查询参数类型 SimpleDelta
的隐式作用域,其中包括伴随对象 SimpleDelta
(请参阅 Scala 文档中的 Implicit Parameters 部分)。
- 编译器在
SimpleDelta
对象中搜索提供 SimpleDelta[String :: Int :: Address :: HNil]
类型对象的值或方法。唯一匹配的方法是 hconsDelta
,因此编译器将参数值 delta
连接到此方法的调用。请注意,HList
被分解为头部和尾部:hconsDelta[String, Int :: Address :: HNil, DT](implicit tailDelta: Aux[Int :: Address :: HNil, DT])
.
- 填写隐式
tailDelta
参数,编译器会不断寻找匹配的隐式方法。这样,HList
被递归解构,直到只剩下 HNil
并调用 hnilDelta
。
- 在"way up"上,
DT
类型的变量会被推断为Diff[Int] :: Diff[Address] :: HNil
,hconsDelta
的return类型会被推断为SimpleDelta[String :: Int :: Address :: HNil] {type Out = Diff[String] :: Diff[Int] :: Diff[Address] :: HNil]}
.因此,apply
的 return 类型被推断为 Diff[String] :: Diff[Int] :: Diff[Address] :: HNil
.
我正在努力学习shapeless,但是我发现Shapeless代码真的很难理解。
所以我从 youtube 上的一次演讲中得到了这个代码示例。
https://www.youtube.com/watch?v=JKaCCYZYBWo
任何人都可以向我解释发生了什么(一步一步)。我发现最困难的是一切都是隐式的,所以很难追踪代码....
另外,请给我指出一些可以帮助我理解这样的代码的资源。每次遇到那么多implicit的代码都觉得自己连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)
- 该程序的目的是比较同一案例的两个实例 class。这是通过异构列表 (
HList
s) 完成的,它可以作为 case classes 的通用表示。 shapeless 提供了在 case class 和它的通用HList
表示之间进行转换的基础设施。请注意,在此示例中,差异不是递归计算的,即任何嵌套案例 class 实例将被自动比较。 - 让我们假设
Diff
特征和伴随对象是不言自明的。 SimpleDelta
特征定义了一个有两个参数的函数 (DepFn2
)。签名是(R, R) => Out
。此外,声明了对结果类型的约束:Out <: HList
。这意味着对于SimpleDelta
的所有子类型,类型成员Out
必须是HList
. 的子类型
- 对象
SimpleDelta
定义类型Aux
。您可以在网上找到有关 Aux 模式的更多信息,例如here。简而言之,它充当SimpleDelta
特征的别名,允许使用类型参数表达Out
类型成员。 - 现在
SimpleDelta
对象定义了两个隐式方法hnilDelta
和hconsDelta
。这些方法中的每一个都代表HList
、HNil
和HCons
的可能构造函数之一。HList
A :: B :: HNil
也可以读作::(A, ::(B, HNil))
或HCons(A, HCons(B, HNil))
。使用这两种方法,可以对输入HList
s进行递归解构和处理。基于这些方法的return类型,编译器在遇到SimpleDelta[R]
类型的隐式参数时知道要隐式解析哪个:当R
被推断为HNil
, 它将调用hnilDelta
;当R
对于某些类型Head
和Tail
被推断为Head :: Tail
(或HCons(Head, Tail)
)时,它将调用hconsDelta
. hnilDelta
方法确定两个空HList
之间的增量(HNil
是空HList
)。- 注意
Out
类型成员设置为HNil
,这是HNil
对象的类型。结果是HNil
,因为没有区别。 - 方法的return类型是
Aux[HNil, HNil]
。如上所述,这是SimpleDelta[HNil] { type Out = HNil }
的别名。它告诉编译器在遇到SimpleDelta[R]
类型的隐式参数时调用此方法,并且R
已被推断为HNil
.
- 注意
hconsDelta
方法确定两个非空HList
之间的增量,即两个HLists
由H
类型的头元素组成,T
类型的尾列表。- 类型参数
DT
表示HList
子类型,表示尾列表的delta。 - 注意隐式参数
tailDelta :Aux[T, DT]
。它表示该方法必须能够确定两个列表l
和r
尾部之间的增量:tailDelta(l.tail, r.tail)
. - 方法的return类型是
Aux[H::T, Diff[H] :: DT]
。如上所述,这是SimpleDelta[H::T] { type Out = Diff[H] :: DT }
的别名。它告诉编译器在遇到SimpleDelta[R]
类型的隐式参数时调用此方法,对于某些类型H
和T
,R
已被推断为H::T
]. - 类型成员
Out
设置为Diff[H] :: DT
,这是结果差异的HList
类型。 apply
方法确定两个头元素之间的差异并将其添加到尾列表之间的差异,这是使用隐式参数tailDelta
计算得出的(见上文)。
- 类型参数
apply
方法负责将类型A
的实例l
和r
转换为它们的通用HList
表示,并调用SimpleDelta
在这些HList
上计算差异。- 类型
R
是A
的通用HList
表示。隐式参数genA: Generic.Aux[A, R]
表示该方法需要在A
和R
之间进行转换。 - 此外,该方法声明了一个隐式参数
delta: SimpleDelta[R]
。这是计算l
和r
的两个通用HList
表示之间的差异所必需的。 - 方法的return类型是
delta.Out
。请注意,此处使用了依赖类型,因为delta
参数的实际Out
类型成员未知 – 它取决于实际类型A
及其HList
表示R
.
- 类型
编译SimpleDelta(homer, ned)
时会发生什么?
- 键入的调用是
SimpleDelta.apply[Character, R](homer, ned)
。 - 编译器尝试为隐式参数
genA: Generic.Aux[Character, R]
和delta: SimpleDelta[R]
. 寻找匹配值
- shapeless 为 case classes
A
提供隐式Generic.Aux[A, R]
。这种机制相当复杂,超出了这个问题的范围,但相关的是类型Character
的通用HList
表示是String :: Int :: Address :: HNil
,因此编译器可以推断类型genA
到Generic.Aux[Character, String :: Int :: Address :: HNil]
。这反过来意味着类型变量R
可以推断为String :: Int :: Address :: HNil
. - 既然类型
R
已知,编译器将尝试解析参数delta: SimpleDelta[R]
的隐式值,即SimpleDelta[String :: Int :: Address :: HNil]
。当前作用域不包含具有此 return 类型的任何隐式方法或具有此类型的值,但编译器还将查询参数类型SimpleDelta
的隐式作用域,其中包括伴随对象SimpleDelta
(请参阅 Scala 文档中的 Implicit Parameters 部分)。 - 编译器在
SimpleDelta
对象中搜索提供SimpleDelta[String :: Int :: Address :: HNil]
类型对象的值或方法。唯一匹配的方法是hconsDelta
,因此编译器将参数值delta
连接到此方法的调用。请注意,HList
被分解为头部和尾部:hconsDelta[String, Int :: Address :: HNil, DT](implicit tailDelta: Aux[Int :: Address :: HNil, DT])
. - 填写隐式
tailDelta
参数,编译器会不断寻找匹配的隐式方法。这样,HList
被递归解构,直到只剩下HNil
并调用hnilDelta
。 - 在"way up"上,
DT
类型的变量会被推断为Diff[Int] :: Diff[Address] :: HNil
,hconsDelta
的return类型会被推断为SimpleDelta[String :: Int :: Address :: HNil] {type Out = Diff[String] :: Diff[Int] :: Diff[Address] :: HNil]}
.因此,apply
的 return 类型被推断为Diff[String] :: Diff[Int] :: Diff[Address] :: HNil
.