HList 的证据保留 LUB 约束
Evidence-preserving LUB constraint for HList
我想我需要一个 HList,它被限制为使其所有元素都是某种类型的子类型。 LUBConstraint
似乎是我想要的,确实它确实限制了这样一个 HList 的 构造 - 但我看不出如何再次获得证据,所以我可以在 HList 上映射(实际上是遍历,因为它需要是单子的)并在每个元素上调用一个方法(存在于 LUB 类型中)。
另外,我希望遍历得到的HList的类型与输入的HList的类型完全一致
用例是一种函数式 "listener list" - HList 的所有元素都是 "listeners" 必须通知 "events",接受或拒绝它们,并且 return 更新了自己的新版本 "internal state"。如果这就是我所需要的,那么我可以只使用一个普通的不可变 Scala 集合。但我也希望在不使用 asInstanceOf
的情况下直接对单个元素进行类型化访问 - 因此尝试使用 HList.
的动机
一般来说,如果您想要对 HList
中的所有元素执行某些操作,您需要将多态函数值映射到 HList
上。例如,假设我有以下设置:
trait Listener[L <: Listener[L]] {
def handle(s: String): Option[L]
}
class FooListener extends Listener[FooListener] {
def handle(s: String) =
if (s.size == 3) Some(this) else None
}
class BarListener extends Listener[BarListener ]{
def handle(s: String) = Some(this)
}
import shapeless._
val listeners = new FooListener :: new BarListener :: HNil
现在我想向每个听众发送 String
并收集结果。如果我只想发送一个固定值,这很简单:
object event123 extends Poly1 {
implicit def listener[L <: Listener[L]] = at[L](_.handle("123"))
}
val result = listeners.map(event123)
这将被适当地键入为 Option[FooListener] :: Option[BarListener] :: HNil
。如果我使用的是 shapeless-contrib,我可以对 HList
:
进行排序
import scalaz._, Scalaz._, shapeless.contrib.scalaz._
val sequenced: Option[FooListener :: BarListener :: HNil] = sequence(result)
或者直接使用 traverse
:
traverse(listeners)(event123)
不幸的是,对于如何定义多态函数值有限制,这意味着部分应用不方便,所以如果我们不知道我们在编译时发送的 String
,这是复杂得多:
object event extends Poly1 {
implicit def listener[L <: Listener[L]] = at[(L, String)] {
case (listener, string) => listener.handle(string)
}
}
traverse(listeners.zip(listeners.mapConst("123")))(event)
我们用字符串压缩了元素,然后映射了一个多态函数,该函数接受结果的元组。您可以使用或多或少相同的方法来执行此操作,但是 none 其中非常清楚。
一个完全不同的方法是跳过多态函数值并定义一个新类型class:
trait Notifiable[L <: HList] {
def tell(s: String)(l: L): Option[L]
}
object Notifiable {
implicit val hnilNotifiable: Notifiable[HNil] = new Notifiable[HNil] {
def tell(s: String)(l: HNil) = Some(HNil)
}
implicit def hconsNotifiable[H <: Listener[H], T <: HList](implicit
tn: Notifiable[T]
): Notifiable[H :: T] = new Notifiable[H :: T] {
def tell(s: String)(l: H :: T) = for {
h <- l.head.handle(s)
t <- tn.tell(s)(l.tail)
} yield h :: t
}
}
def tell[L <: HList: Notifiable](s: String)(l: L) =
implicitly[Notifiable[L]].tell(s)(l)
然后:
val sequenced: Option[FooListener :: BarListener :: HNil] =
tell("123")(listeners)
这不太通用(它只适用于 Option
,不适用于任意应用程序),但它不需要额外的排序依赖性,而且可以说它比跳过箍到部分由于编译器的奇怪限制,应用多态函数值。
我想我需要一个 HList,它被限制为使其所有元素都是某种类型的子类型。 LUBConstraint
似乎是我想要的,确实它确实限制了这样一个 HList 的 构造 - 但我看不出如何再次获得证据,所以我可以在 HList 上映射(实际上是遍历,因为它需要是单子的)并在每个元素上调用一个方法(存在于 LUB 类型中)。
另外,我希望遍历得到的HList的类型与输入的HList的类型完全一致
用例是一种函数式 "listener list" - HList 的所有元素都是 "listeners" 必须通知 "events",接受或拒绝它们,并且 return 更新了自己的新版本 "internal state"。如果这就是我所需要的,那么我可以只使用一个普通的不可变 Scala 集合。但我也希望在不使用 asInstanceOf
的情况下直接对单个元素进行类型化访问 - 因此尝试使用 HList.
一般来说,如果您想要对 HList
中的所有元素执行某些操作,您需要将多态函数值映射到 HList
上。例如,假设我有以下设置:
trait Listener[L <: Listener[L]] {
def handle(s: String): Option[L]
}
class FooListener extends Listener[FooListener] {
def handle(s: String) =
if (s.size == 3) Some(this) else None
}
class BarListener extends Listener[BarListener ]{
def handle(s: String) = Some(this)
}
import shapeless._
val listeners = new FooListener :: new BarListener :: HNil
现在我想向每个听众发送 String
并收集结果。如果我只想发送一个固定值,这很简单:
object event123 extends Poly1 {
implicit def listener[L <: Listener[L]] = at[L](_.handle("123"))
}
val result = listeners.map(event123)
这将被适当地键入为 Option[FooListener] :: Option[BarListener] :: HNil
。如果我使用的是 shapeless-contrib,我可以对 HList
:
import scalaz._, Scalaz._, shapeless.contrib.scalaz._
val sequenced: Option[FooListener :: BarListener :: HNil] = sequence(result)
或者直接使用 traverse
:
traverse(listeners)(event123)
不幸的是,对于如何定义多态函数值有限制,这意味着部分应用不方便,所以如果我们不知道我们在编译时发送的 String
,这是复杂得多:
object event extends Poly1 {
implicit def listener[L <: Listener[L]] = at[(L, String)] {
case (listener, string) => listener.handle(string)
}
}
traverse(listeners.zip(listeners.mapConst("123")))(event)
我们用字符串压缩了元素,然后映射了一个多态函数,该函数接受结果的元组。您可以使用或多或少相同的方法来执行此操作,但是 none 其中非常清楚。
一个完全不同的方法是跳过多态函数值并定义一个新类型class:
trait Notifiable[L <: HList] {
def tell(s: String)(l: L): Option[L]
}
object Notifiable {
implicit val hnilNotifiable: Notifiable[HNil] = new Notifiable[HNil] {
def tell(s: String)(l: HNil) = Some(HNil)
}
implicit def hconsNotifiable[H <: Listener[H], T <: HList](implicit
tn: Notifiable[T]
): Notifiable[H :: T] = new Notifiable[H :: T] {
def tell(s: String)(l: H :: T) = for {
h <- l.head.handle(s)
t <- tn.tell(s)(l.tail)
} yield h :: t
}
}
def tell[L <: HList: Notifiable](s: String)(l: L) =
implicitly[Notifiable[L]].tell(s)(l)
然后:
val sequenced: Option[FooListener :: BarListener :: HNil] =
tell("123")(listeners)
这不太通用(它只适用于 Option
,不适用于任意应用程序),但它不需要额外的排序依赖性,而且可以说它比跳过箍到部分由于编译器的奇怪限制,应用多态函数值。