是HLists无非就是写一个元组的令人费解的方式吗?(Are HLists nothing more

2019-06-17 15:55发布

我在找出其中的差异,以及更普遍,以查明HLists不能使用(或者说,没有产生过常规列表的任何利益)的规范使用情况很感兴趣。

(据我所知,有22个(我相信) TupleN在Scala中,而一个只需要一个单一的HList,但不是那种概念上的差异我感兴趣的。)

我已经打上了几个在下面的文本问题。 它可能不是真正需要回答这些问题,他们更意味着要指出的东西都是我不清楚,并指导在某些方向的讨论。

动机

我最近看到一对夫妇的答案对SO那里的人建议使用HLists(例如,通过提供无形的 ),包括已删除的回答这个问题 。 这引起了讨论 ,而这又引发了这个问题。

介绍

在我看来,当你知道的元素和它们的精确类型的静态数量hlists是唯一有用的。 数量实际上并不重要,但它似乎不太可能,你需要生成具有不同的元素,但恰恰静态已知类型的列表,但你不知道静态它们的数量。 问题1:你甚至可以写出这样的例子,例如,在一个循环? 我的直觉是,具有与静态未知数目任意元件(任意相对于给定类层次结构)静态精确hlist只是不兼容。

HLists与元组

如果这是真的,也就是说,你知道静态数量和类型的- 问题2:为什么不使用n元组? 当然,你可以typesafely地图和折叠的HList(你也可以,但不是 typesafely,做主持的帮助下一个元组productIterator ),但由于是静态已知你很可能只是访问的元组的元素的数量和类型元件直接和执行的操作。

在另一方面,如果函数f你映射了一个hlist是很通用,它接受所有元素- 问题3:为什么通过不使用它productIterator.map ? 好吧,一个有趣的差异可能来自重载方法:如果我们有几个重载f的,具有由hlist(相较于productIterator)提供更强的类型信息可以让编译器选择更具体的f 。 但是,我不知道是否会在实际工作斯卡拉,因为方法和功能是不一样的。

HLists和用户输入

在同样的假设大厦,即,你需要知道的静态元素的数量和类型- 问题4:可以hlists可以在情况下使用,其中的要素依赖于任何类型的用户互动? 例如,想象填充与循环内元素的hlist; 该元件从某处(UI,配置文件,演员相互作用,网络),直到在一定条件成立读取。 会是什么hlist的类型是什么? 为一种接口规范,类似的getElements:HList [...]应与静态未知长度的名单运作,并允许成分A的系统,以获得从成分B的任意元素的这样一个清单

Answer 1:

解决问题一至三个:针对主要应用之一, HLists被抽象了元数。 元数通常在静态的抽象的任何使用网站知道,但是从现场到不同网站。 借此,从无形的例子 ,

def flatten[T <: Product, L <: HList](t : T)
  (implicit hl : HListerAux[T, L], flatten : Flatten[L]) : flatten.Out =
    flatten(hl(t))

val t1 = (1, ((2, 3), 4))
val f1 = flatten(t1)     // Inferred type is Int :: Int :: Int :: Int :: HNil
val l1 = f1.toList       // Inferred type is List[Int]

val t2 = (23, ((true, 2.0, "foo"), "bar"), (13, false))
val f2 = flatten(t2)
val t2b = f2.tupled
// Inferred type of t2b is (Int, Boolean, Double, String, String, Int, Boolean)

如果不使用HLists (或等价的东西),以抽象的过度元组参数的元数,以flatten就不可能有一个单一的实现可能接受这两个非常不同的形状的参数,并在类型安全的方式改造他们。

在元数的能力,抽象很可能是感兴趣的任何地方,固定arities涉及:以及元组,如上述,其包括方法/函数的参数列表,和case类。 见这里的例子我们怎样才能在抽象任意case类几乎自动获取类型的类实例的元数,

// A pair of arbitrary case classes
case class Foo(i : Int, s : String)
case class Bar(b : Boolean, s : String, d : Double)

// Publish their `HListIso`'s
implicit def fooIso = Iso.hlist(Foo.apply _, Foo.unapply _)
implicit def barIso = Iso.hlist(Bar.apply _, Bar.unapply _)

// And now they're monoids ...

implicitly[Monoid[Foo]]
val f = Foo(13, "foo") |+| Foo(23, "bar")
assert(f == Foo(36, "foobar"))

implicitly[Monoid[Bar]]
val b = Bar(true, "foo", 1.0) |+| Bar(false, "bar", 3.0)
assert(b == Bar(true, "foobar", 4.0))

有没有运行迭代这里,但有重复 ,它使用的HLists (或等同结构)可以消除。 当然,如果你对重复的样板耐受性高,可以通过编写每个多种实现和每一个你关心形状得到相同的结果。

在问题三你问“......如果f你映射了一个hlist的功能是如此的普通,它接受所有元素......为什么不通过productIterator.map使用它呢?”。 如果映射在一个HList功能真的是形式的Any => T则在映射productIterator将竭诚为您服务得很好。 但形式的功能Any => T通常不那么有趣(至少,它们不是除非它们型内转换)。 不成形提供多态函数值的一种形式,它允许编译器恰好你怀疑的方式选择特定类型的案件。 例如,

// size is a function from values of arbitrary type to a 'size' which is
// defined via type specific cases
object size extends Poly1 {
  implicit def default[T] = at[T](t => 1)
  implicit def caseString = at[String](_.length)
  implicit def caseList[T] = at[List[T]](_.length)
}

scala> val l = 23 :: "foo" :: List('a', 'b') :: true :: HNil
l: Int :: String :: List[Char] :: Boolean :: HNil =
  23 :: foo :: List(a, b) :: true :: HNil

scala> (l map size).toList
res1: List[Int] = List(1, 3, 2, 1)

关于你的问题四,关于用户输入,有两种情况需要考虑。 第一种情况是,我们可以动态建立这保证了一个已知的静态条件获得的上下文的情况。 在这些类型的方案,这是完全可以应用无形的技术,但显然与如果静止状态并不在运行时获得那么我们必须遵循的替代路径的条件。 勿庸置疑,这意味着其是动态条件敏感的方法具有以产生可选的结果。 下面是使用一个例子HList S,

trait Fruit
case class Apple() extends Fruit
case class Pear() extends Fruit

type FFFF = Fruit :: Fruit :: Fruit :: Fruit :: HNil
type APAP = Apple :: Pear :: Apple :: Pear :: HNil

val a : Apple = Apple()
val p : Pear = Pear()

val l = List(a, p, a, p) // Inferred type is List[Fruit]

的类型的l不捕获该列表的长度,或精确的类型的元素。 但是,如果我们期望它有特定的形式(即,如果它应该符合一些已知的固定模式),那么我们可以尝试建立这一事实,并采取相应的行动,

scala> import Traversables._
import Traversables._

scala> val apap = l.toHList[Apple :: Pear :: Apple :: Pear :: HNil]
res0: Option[Apple :: Pear :: Apple :: Pear :: HNil] =
  Some(Apple() :: Pear() :: Apple() :: Pear() :: HNil)

scala> apap.map(_.tail.head)
res1: Option[Pear] = Some(Pear())

还有其他一些情况,我们可能不会在乎一个给定的列表的实际长度,比它的长度相同其他一些列表等。 再次,这是一件不成形载体,既充分静态,并且还以混合的静态/动态上下文如上。 见这里更深入的例子。

这是真的,因为你看到,所有这些机制都需要静态类型信息可用,至少是条件,而这似乎排除在一个完全动态的环境中而使用这些技术,由外部提供的非类型化数据完全开动。 但随着运行时编译为2.10 Scala的反射组件支持的来临,即使这不再是一个不可逾越的障碍......我们可以利用运行时编译提供的一种形式轻量级分期和有我们的静态类型在运行时进行响应于动态数据:摘录从下方前述...按照链接为全例如

val t1 : (Any, Any) = (23, "foo") // Specific element types erased
val t2 : (Any, Any) = (true, 2.0) // Specific element types erased

// Type class instances selected on static type at runtime!
val c1 = stagedConsumeTuple(t1) // Uses intString instance
assert(c1 == "23foo")

val c2 = stagedConsumeTuple(t2) // Uses booleanDouble instance
assert(c2 == "+2.0")

我敢肯定, @PLT_Borat将有话要说,鉴于他的约依赖性类型编程语言圣人评论 ;-)



Answer 2:

只是要清楚,一个HList本质上不过一叠更Tuple2 ,顶部略有不同的糖。

def hcons[A,B](head : A, tail : B) = (a,b)
def hnil = Unit

hcons("foo", hcons(3, hnil)) : (String, (Int, Unit))

所以你的问题基本上是关于使用嵌套元组VS平元组之间的差异,但两者是同构那么,到底真的是方便之外没有什么区别,其中库函数可用于和符号可以使用。



Answer 3:

有很多事情你不能做(孔)元组:

  • 写一个通用的前置/附加功能
  • 写反函数
  • 写一个concat函数
  • ...

你可以做所有这一切当然是有元组,而不是在一般情况下。 因此,使用HLists使你的代码更加干燥。



Answer 4:

我可以在超级简单的语言解释:

元组VS列表命名并不显著。 HLists可能被命名为HTuples。 所不同的是,在斯卡拉+ Haskell中,你可以用一个元组(使用Scala的语法)做到这一点:

def append2[A,B,C](in: (A,B), v: C) : (A,B,C) = (in._1, in._2, v)

采取任何类型的正好两个要素的输入元组,附加的第三元件,并且与正好三个元件返回一个完整类型的元组。 不过,虽然这是在类型完全通用的,它必须显式地指定输入/输出长度。

什么是Haskell的风格HList让你做的是让这个普通的在长度,这样你就可以追加到元组/列表的任意长度,并得到一个完全静态类型的元组/列表。 这样做的好处也适用于均匀类型的集合,在这里您可以追加一个int到刚好n INTS的名单,并取回那是静态类型的列表,以便准确地(N + 1)整数而不显式指定ñ。



文章来源: Are HLists nothing more than a convoluted way of writing tuples?