【问题标题】:Extractor for a shapeless HList that mimics parser concatenation `~`模拟解析器连接的无形 HList 的提取器`~`
【发布时间】:2013-08-30 09:18:18
【问题描述】:

问题

是否有可能为无形'HList 创建一个提取器,如下所示。

val a ~ _ ~ b = 4 :: "so" :: 4.5 :: HNil
=> a == 4 && b == 4.5
  1. :: 替换为~,这应该不是问题。
  2. 去掉终止的HNil。有没有可能出现的问题?

动机

经过大量的汗水和泪水,我终于到达了以下代码的工作点:

for(
  x1 :: _ :: x2 :: HNil <- (expInt ~ "+" ~ expInt).llE
) yield (x1 + x2)

expInt 在某个 monad E 中解析一个 Int(expInt ~ "+" ~ expInt).llE 的类型是E[Int :: String :: Int :: HNil]

我希望&lt;- 左侧的模式在某种程度上类似于右侧组合子解析器的构造。

【问题讨论】:

  • 乍一看,我怀疑提取器语法是不可能的,但这是一个有价值的目标,祝你好运!
  • 顺便说一句,我很想看看你是如何在你的项目中使用 shapeless ... github 链接?
  • @Miles 目前这是一团糟。我刚刚将我正在开发的功能提取到它自己的库中,我正在整理这些东西。一旦有人能理解它,我打算把它放在 Github 上。它将是一个有助于创建命令行工具(解析器部分)以运行计算实验(monad 部分)的库;比如在不同的参数下用不同的算法解决问题。我已经把提醒你放在我的待办事项清单上。

标签: scala parser-combinators shapeless hlist


【解决方案1】:

这是可以做到的,并且有几个有趣的转折点。

第一个是,通常,为了匹配使用右关联构造函数(即::)构建的结构,您将使用相应的右关联提取器,否则您将以相反的顺序分解和绑定提取的元素。不幸的是,右关联提取器必须像右关联运算符一样,在 Scala 中以 : 结尾,这会破坏您的解析器组合器语法,因为提取器名称必须是 ~: 而不是普通的 ~。不过,我将暂时推迟此操作并使用正确的关联性。

第二个转折点是,我们需要 unapply 方法来产生不同类型的结果,具体取决于我们是匹配 超过两个元素的 HList 还是 恰好 em> 两个元素(我们根本不应该匹配少于两个元素的列表)。

如果我们要匹配一个包含两个以上元素的列表,我们需要将列表分解为由头部和HList 尾部组成的一对,即。给定l: H :: T 其中T &lt;: HList 我们必须产生(H, T) 类型的值。另一方面,如果我们匹配一个恰好包含两个元素的列表,即。对于E1 :: E2 :: HNil 的形式,我们需要将列表分解为仅由这两个元素组成的一对,即(E1, E2),而不是(E1, E2 :: HNil) 的头部和尾部。

这可以使用与无形中使用的完全相同的类型级编程技术来完成。首先,我们定义一个类型类来完成提取器的工作,其实例对应于上述两种情况,

import shapeless._

trait UnapplyRight[L <: HList] extends DepFn1[L]

trait LPUnapplyRight {
  type Aux[L <: HList, Out0] = UnapplyRight[L] { type Out = Out0 }
  implicit def unapplyHCons[H, T <: HList]: Aux[H :: T, Option[(H, T)]] =
    new UnapplyRight[H :: T] {
      type Out = Option[(H, T)]
      def apply(l: H :: T): Out = Option((l.head, l.tail))
    }
}

object UnapplyRight extends LPUnapplyRight {
  implicit def unapplyPair[H1, H2]: Aux[H1 :: H2 :: HNil, Option[(H1, H2)]] =
    new UnapplyRight[H1 :: H2 :: HNil] {
      type Out = Option[(H1, H2)]
      def apply(l: H1 :: H2 :: HNil): Out = Option((l.head, l.tail.head))
    }
}

然后我们像这样定义我们的提取器,

object ~: {
  def unapply[L <: HList, Out](l: L)
    (implicit ua: UnapplyRight.Aux[L, Out]): Out = ua(l)
}

然后我们就可以走了,

val l = 23 :: "foo" :: true :: HNil

val a ~: b ~: c = l
a : Int
b : String
c : Boolean

到目前为止,一切都很好。现在让我们回到关联性问题。如果我们想用左关联提取器(即~ 而不是~:)实现相同的效果,我们需要改变分解的方式。首先让我们对刚刚使用的右关联提取器语法进行去糖处理。表达式,

val a ~: b ~: c = l

等价于,

val ~:(a, ~:(b, c)) = l

相比之下,左联想版本,

val a ~ b ~ c = l

等价于,

val ~(~(a, b), c) = l

为了使它作为HLists 的提取器工作,我们的 unapply 类型类必须从列表的末尾而不是从开头剥离元素。我们可以借助 shapeless 的 InitLast 类型类来做到这一点,

trait UnapplyLeft[L <: HList] extends DepFn1[L]

trait LPUnapplyLeft {
  import ops.hlist.{ Init, Last }
  type Aux[L <: HList, Out0] = UnapplyLeft[L] { type Out = Out0 }
  implicit def unapplyHCons[L <: HList, I <: HList, F]
    (implicit
      init: Init.Aux[L, I],
      last: Last.Aux[L, F]): Aux[L, Option[(I, F)]] =
    new UnapplyLeft[L] {
      type Out = Option[(I, F)]
      def apply(l: L): Out = Option((l.init, l.last))
    }
}

object UnapplyLeft extends LPUnapplyLeft {
  implicit def unapplyPair[H1, H2]: Aux[H1 :: H2 :: HNil, Option[(H1, H2)]] =
    new UnapplyLeft[H1 :: H2 :: HNil] {
      type Out = Option[(H1, H2)]
      def apply(l: H1 :: H2 :: HNil): Out = Option((l.head, l.tail.head))
    }
}

object ~ {
  def unapply[L <: HList, Out](l: L)
    (implicit ua: UnapplyLeft.Aux[L, Out]): Out = ua(l)
}

现在我们完成了,

val a ~ b ~ c = l
a : Int
b : String
c : Boolean

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-29
    • 1970-01-01
    • 2014-12-12
    • 2020-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多