【问题标题】:Evidence-preserving LUB constraint for HListHList 的保证据 LUB 约束
【发布时间】:2015-07-31 01:34:20
【问题描述】:

我想我需要一个 HList,它的所有元素都是某种类型的子类型。 LUBConstraint 似乎是我想要的,实际上它确实限制了这样一个 HList 的 构造 - 但我看不到如何再次获取证据,以便我可以映射(实际上,遍历,因为它需要是单子的)在 HList 上并在每个元素上调用一个方法(存在于 LUB 类型中)。

另外,我希望遍历操作产生的 HList 的类型与输入 HList 的类型完全相同。

用例是一种功能性的“监听器列表”——HList 的所有元素都是“监听器”,它们必须被通知“事件”,接受或拒绝它们,并返回更新后的新版本“内部状态”。如果这就是我所需要的,那么我可以只使用一个普通的不可变 Scala 集合。但我也希望在不使用 asInstanceOf 的情况下直接键入访问单个元素 - 因此是尝试使用 HList 的动机。

【问题讨论】:

  • 我不确定我是否理解正确,但是如果您遍历列表并使用对 LUB 类型的方法调用的结果修改每个元素,那么要获得与您相同类型的结果肯定需要在每个元素上调用 asInstanceOf。

标签: scala shapeless hlist


【解决方案1】:

一般来说,如果您想对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)

我们用字符串压缩了元素,然后映射了一个多态函数,该函数将元组作为结果。还有其他方法可以使用或多或少相同的方法来做到这一点,但它们都不是非常清楚。

一种完全不同的方法是跳过多态函数值并定义一个新的类型类:

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,不适用于任意应用程序),但它不需要额外的排序依赖,并且可以说它比跳过箍部分应用多态函数更容易值,因为编译器的奇怪限制。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    • 2015-09-30
    相关资源
    最近更新 更多