【问题标题】:Scala Advanced Type UsageScala 高级类型用法
【发布时间】:2013-09-15 21:24:39
【问题描述】:

背景

我正在使用 Scala 开发一个事件库。 In my library 你可以这样定义事件:

val e1 = new ImperativeEvent[Int]

你可以这样触发它们:

e1(42)

您可以像这样创建反应:

val r1 = (i: Int) => println(i)

并将它们附加到这样的事件中:

e1 += r1

还有一些其他的东西(比如事件转换、合成等)。我使用Esper CEP engine 作为我图书馆的后端。 Esper 对大多数操作使用一种称为 EPL 的类似 SQL 的语言。

问题

我正在尝试实现一些更高级的概念,例如事件连接。所以现在你可以像这样定义具有多个属性的事件(使用元组类型):

val e2 = new ImperativeEvent[(Int, String)]

然后像这样加入他们:

val e3 = e1 join e2 windowLength (30) on "E1.P1 = E2.P1"

在它们各自的第一个属性相等的条件下,对最后 30 次出现的 e1 和 e2 执行连接。

这没关系,但我想在我的实现中去掉字符串以使事件表达式类型可检查。我想把连接表达式改成这样:

val e3 = e1 join e2 windowLength (30) on e1._1 === e2._1

类似于它在例如完成的方式。 Squeryl。这样做的问题是,我无法访问元组类型的元素的类型...

问题

如何静态访问元组类型?现在我只能通过反射在运行时访问它们,这对我没有帮助。我很确定元组无法实现我想要实现的目标,但我想知道使用 shapeless 库中的 HLists 或类似的东西是否有助于实现我的目标。

【问题讨论】:

  • 好像你有e1.join(e2).windowLength(30).on(e1._1 === e2._1)=== 是什么?
  • += 对于您在这里使用的操作符来说,这似乎是一个奇怪的选择。这意味着 ImperativeEvent 纯粹或主要是反应的集合。如果你重写了 + 来添加任何你可能添加到事件中的各种东西,我想这会更容易理解,但它仍然让我感到不安。
  • @pedrofurla E1 指连接的左侧事件,E2 指右侧连接事件。 join 方法用适当的内部事件 ID 替换那些。实际的连接是由 Esper (esper.codehaus.org) 完成的,我的库主要是它的包装器。 Esper 基于一种称为 EPL 的类似 SQL 的语言,这就是它使用字符串的原因。我想为这些字符串添加类型检查(类似于例如 Squeryl,这就是我的 === 示例的来源)但我不确定这是否可以通过我当前的事件表示(其中参数表示为(元组)事件类型)。
  • @itsbruce += 运算符的想法是从 C# 借来的。 Check this out 快速介绍 C# 事件。
  • 所以有先例,我明白了,您正在为 C# 退伍军人提供熟悉的用法。不过,这是一个糟糕的先例;)如果它调用了一个名为 registerHandler 之类的方法,那我每次都会使用它。

标签: scala types shapeless


【解决方案1】:

如果没有关于您的 DSL 的更多详细信息,恐怕不清楚您所说的“静态访问元组类型”是什么意思。这是一个简化的 API 版本,它对元组类型没有任何问题:

class Event[T] {
  def joinOn[T2, R](ev2: Event[T2])(f: (T, T2) => R) = new Event[R]
}

您可以按如下方式使用它:

val e1 = new Event[(Int, String)]
val e2 = new Event[(Int, String)]
val e3 = e1.joinOn(e2)(_._1 == _._2)

应该很容易看出如何将其扩展为支持您的 join/windowLength/on 语法。

更新: 我可以看到您的用例很复杂,因为您需要将 Scala 编码的查询表达式翻译成另一种查询语言。在这种情况下,您希望 on 方法的签名看起来像:

def on[T2, R](f: (Expr[T], Expr[T2]) => Expr[R]): Event[R]

在内部,每个事件对象都将创建自己的 Expr 表示,并将此表示传递给提供给 on 方法的函数。

Expr 类型可以这样定义:

trait Expr[T] {
  protected val repr: String

  def _1[A](implicit ev: T <:< Tuple2[A,_]): Expr[A] = 
    ??? // create an Expr[A] whose string representation is (repr + ".P1")

  // abstracting over tuple arities (using Shapeless)
  import shapeless._, nat._
  @scala.annotation.implicitNotFound("A tuple with at least 3 elements is required")
  type At2 = ops.tuple.At[T, _2]

  def _3(implicit at: At2): Expr[at.Out] = 
    ??? // create an Expr[at.Out] whose string representation is (repr + ".P3")

  def ===(other: Expr[T]): Expr[Boolean] =
    ??? // create an Expr[T] whose string representation is (repr + " = " + other.repr)
}

这显然大大简化了,但应该有助于您入门。

【讨论】:

  • 现在我看到了解决方案,它看起来非常简单......谢谢!但是,我还有一个问题:正如您从上面的示例中看到的那样,我实际上需要在最后创建一个字符串......它应该包含元素在元组中的位置而不是实际元素本身(例如,您的示例 _._1 == _._2 应该返回一个字符串“E1.P1 = E2.P2”)。我可能需要通过隐式来丰富 Int 和 String 类型,但我不确定获取元组中元素的位置...
  • 我添加了一些关于 API 如何计算字符串表示的注释。
  • 我没有考虑到这一点,但这是研究 Shapeless 的另一个原因。或者,您可以将 Tuple2Expr[T1,T2]、Tuple3Expr[T1,T2,T3] 等定义为 Expr[(T1,T2)]、Expr[(T1,T2,T3)] 等的隐式视图。 , 分别。这种非无形方法需要 22 个类和 253 个方法定义!
  • 我最近对学习 Shapeless 很感兴趣,所以我决定研究一下。结果很简单——请看我最近的编辑,它定义了一个中性的 _3 方法,如果客户端代码无法提供合适的元组类型,它甚至会给出一个有用的编译器错误。
  • 感谢您的建议!我对你的建议有一些问题,但在对 shapeless 的测试用例进行了一些实验和探索之后,我得到了它的工作,我的版本看起来像这样:def _2(implicit at: At[T, _1]) = ValueExpr[at.Out](repr + ".P3") 注意at.Out 部分;在您的版本中,元素类型设置不正确。使用 Shapeless 的一个小缺点是 IDE 支持存在一些问题。
【解决方案2】:

我。有一个SynapseGrid 函数式反应式编程库。在资源中,您可以找到一些有用的提示。

库中的所有处理都是类型安全的。您可以完全访问元组。

例如,如果我必须在 SynapseGrid 中实现 join,我将定义一个方法 join,其签名如下:

implicit class RichContact[T] (c:Contact[T]){ // contact == event in SynapseGrid's terminology
  ...
  def join[T2](c2:Contact[T2]):Contact[(T, T2)] = {
    // construct a contact/event that do nothing more than joining two events.
  }
}

implicit class RichTupledContact[T, T2](c:Contact[(T, T2)])
  def windowLength(len:Int):Contact[(T, T2)] = { 
    // construct the next step of processing events — window 
  }
}

等等。逐步构建事件处理大大简化了系统的构建。

二。但是,如果您需要一次构造所有内容,那么您可能会返回一些具有构造方法的中间对象:

implicit class RichContact[T] (c:Contact[T]){ // contact == event in SynapseGrid's terminology
  ...
  def join[T2](c2:Contact[T2]):Contact[(T, T2)] = {
    new {
      def windowLength(len:Int) = ...
    }
  }
}

【讨论】:

    猜你喜欢
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-24
    • 2017-09-12
    • 2015-03-08
    • 2022-12-11
    • 2011-09-08
    相关资源
    最近更新 更多