记录为参数,组成

Records as parameters, composition

编辑:我将问题重新表述为更简单且不那么特定领域的问题: 在下面的代码中,我想实现 mplus 函数,它结合了两个受特定字段存在限制的函数。生成的函数应受两个字段的存在限制。 谢谢!

import shapeless._, ops.record.Selector, record._, syntax.singleton._

def requiresIntervalKey[L <: HList](l: L)
                                   (implicit sel: Selector.Aux[L, Witness.`"interval"`.T, Int]): Unit = {
  println(sel(l))
}
def requiresPlatformField[L <: HList](l: L)
                                     (implicit sel: Selector.Aux[L, Witness.`"platform"`.T, String]): Unit = {
  println(sel(l))
}
def mplus = ??? // That is the function I'd like to implement. Eventually it will be the additive operator of a monoid

// needsBothFields: L <: HList -> (implicit) Selector.Aux[L, Witness.`"interval"`.T, Int] -> (implicit) Selector.Aux[L, Witness.`"platform"`.T, String] -> Unit
val needsBothField = mplus(requiresIntervalKey _, requiresIntervalKey _ )

// Usage
requiresIntervalKey(("interval" ->> "a string") :: HNil) // Shoudn't compile, value type is wrong
requiresIntervalKey(("wrongKey" ->> "a string") :: HNil) // Shoudn't compile, "interval" key not provided
requiresIntervalKey(("interval" ->> 42) :: HNil) // Compiles
requiresPlatformField(("platform" ->> "EU") :: HNil) // Compiles
needsBothFields(("interval" ->> 42) :: ("platform" ->> "EU") :: HNil) // Should compile

// 上一题版本

对无形和类型级编程还很陌生,我正在努力实现我的 objective 并获得清晰的理解。非常感谢您的帮助!

我基本上有几个函数如下所示:

trait PathGenerator[T] {
  def apply(T): Set[Seq[String]]
}

val hourlyIntervalPathGenerator: PathGenerator[Interval] = ???
val constantGenerator: PathGenerator[String] = ???

// A Monoid[PathGenerator]
// an implicit class adding a '/' operator to PathGenerator

// Usage
val hourlyRegionPathGenerator = hourlyIntervalPathGenerator / constantGenerator
val paths = hourlyRegionPathGenerator(StaticIntervals.lastDay, "EU")

现在,当将两个以上的生成器加在一起时,用户必须处理嵌套的元组。此外,命名生成器参数对于生成解析器非常有用(例如:cmd-line。)

无形记录似乎很合适,所以我继续实施了以下无效解决方案: https://gist.github.com/amazari/911449b55270a5871d14

这个不可构建的代码和我无形的误解引起了几个问题:

那么,记录是实现此用例的正确工具吗?代码片段中未显示的是从 PathPattern 的记录 shape/template 生成的命令行解析器。

我如何强制提供给生成器(简单的或由多个生成器组合产生)的记录在名称和类型方面具有完全正确的字段?

感谢您的帮助!

编辑:这一系列问题和 Travis Brown 的回答非常相关:

Passing a Shapeless Extensible Record to a Function Passing a Shapeless Extensible Record to a Function (continued)

可以通过 Shapeless 实现,尽管与您要求的形式不完全相同。这是一个简单的例子:

import shapeless._, ops.record.Selector, record._, syntax.singleton._

class UseKey[K <: String, V, R](w: Witness.Aux[K])(f: V => R) extends Poly1 {
  implicit def onRecord[L <: HList](implicit
    sel: Selector.Aux[L, K, V]
  ): Case.Aux[L, R] = at[L](l => f(sel(l)))
}

class Combine[P1 <: Poly1, P2 <: Poly1, R1, R2, R](
  p1: P1,
  p2: P2
)(f: (R1, R2) => R) extends Poly1 {
  implicit def onRecord[L <: HList](implicit
    c1: p1.Case.Aux[L, R1],
    c2: p2.Case.Aux[L, R2]
  ): Case.Aux[L, Unit] = at[L](l => f(c1(l), c2(l)))
}

class mplus[P1 <: Poly1, P2 <: Poly1](p1: P1, p2: P2)
  extends Combine(p1, p2)((_: Unit, _: Unit) => ())

然后:

object requiresIntervalKey extends UseKey(Witness("interval"))(
  (i: Int) => println(i)
)

object requiresPlatformField extends UseKey(Witness("platform"))(
  (s: String) => println(s)
)

object needsBothFields extends mplus(requiresIntervalKey, requiresPlatformField)

(作为旁注,我猜你可以在这里写 UseKey("platform"),但由于某种原因,捕获见证的隐式转换不起作用。幸运的是 Witness("platform")太糟糕了。)

接下来要证明它有效:

scala> import test.illTyped
import test.illTyped

scala> illTyped("""requiresIntervalKey("interval" ->> "a string" :: HNil)""")

scala> illTyped("""requiresIntervalKey("wrongKey" ->> "a string" :: HNil)""")

scala> requiresIntervalKey("interval" ->> 42 :: HNil)
42

scala> requiresPlatformField("platform" ->> "EU" :: HNil)
EU

scala> needsBothFields("interval" ->> 42 :: "platform" ->> "EU" :: HNil)
42
EU

我们需要 Poly1 而不是常规旧函数的原因是我们无法收集组合普通函数时所需的不同证据——相反,我们需要多态函数值(Shapeless 提供为 PolyN).

还值得注意的是,这并不是真正的 "monoid"。类型的幺半群实例提供了一个操作,该操作采用该类型的两个值和 returns 另一个值。这里的 mplus 接受两个操作,每个操作都有自己的隐含要求,并给我们另一个具有两者组合要求的操作。