Scala高级类型用法

7

背景

我正在使用Scala编写事件库。 在我的库中,您可以像这样定义事件:

val e1 = new ImperativeEvent[Int]

你可以像这样触发它们:
e1(42)

您可以像这样创建反应:
val r1 = (i: Int) => println(i)

将它们附加到事件上,就像这样:
e1 += r1

除了事件转换、组合等其他内容外,我使用Esper CEP引擎作为我的库的后端。 Esper在大多数操作中使用类似SQL的语言称为EPL。

问题

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

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

然后像这样加入它们:
val e3 = e1 join e2 windowLength (30) on "E1.P1 = E2.P1"

该语句对e1和e2进行联接,条件是它们各自的第一个属性相等,并且只考虑它们在最后30次出现时的情况。

这样做没问题,但我想去掉实现中的字符串,以使事件表达式可以进行类型检查。我想将联接表达式更改为以下内容:

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

类似于在Squeryl中所做的方式。问题在于,我无法访问元组类型的元素类型......

问题

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


1
看起来你有 e1.join(e2).windowLength(30).on(e1._1 === e2._1)=== 是什么意思? - pedrofurla
“+=”在这里似乎是一个奇怪的操作符选择。它暗示了“ImperativeEvent”纯粹或主要是一组反应。如果您已经覆盖了“+”,以添加到事件中可能添加的各种内容之一,我猜那可能会更容易理解,但它仍然使我感到不安。 - itsbruce
@pedrofurla E1 指连接的左事件,E2 指右事件。join 方法将它们替换为适当的内部事件 ID。实际的连接是由 Esper(http://esper.codehaus.org/)完成的,我的库主要是它的包装器。Esper 基于一种名为 EPL 的类 SQL 语言,因此它使用字符串。我想为这些字符串添加类型检查(类似于 Squeryl,这就是我的 === 示例来自的地方),但我不确定是否可以在当前事件表示法中实现(其中参数表示为事件的元组类型)。 - Mark Goldenstein
1
@itsbruce += 运算符的想法是从 C# 借鉴而来的。点击这里 快速了解 C# 事件。 - Mark Goldenstein
1
我看到了先例,你向C#老手提供了熟悉的用法。虽然这是个不好的先例 ;) 如果它调用一个名为registerHandler的方法,那每次我都会使用它。 - itsbruce
显示剩余2条评论
2个回答

4

如果您的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类型,但我不确定如何获取元组中元素的位置... - Mark Goldenstein
1
我添加了一些关于API如何计算字符串表示的注释。 - Aaron Novstrup
1
我之前没有考虑过这一点,但这是进一步研究Shapeless的另一个原因。或者,您可以将Tuple2Expr[T1,T2]、Tuple3Expr[T1,T2,T3]等定义为Expr[(T1, T2)]、Expr[(T1,T2,T3)]等的隐式视图。这种非Shapeless方法需要22个类和253个方法定义! - Aaron Novstrup
1
最近我对学习Shapeless产生了兴趣,于是决定深入了解。结果发现这相当简单--请看我的最近编辑,其中定义了一个元数中立的_3方法,甚至在客户端代码未能提供适当的元组类型时会给出有用的编译器错误提示。 - Aaron Novstrup
1
谢谢您的建议!我在您的建议上遇到了一些问题,但经过一些实验和对shapeless测试用例的探索,我终于让它工作了,我的版本看起来像这样:def _2(implicit at: At[T, _1]) = ValueExpr[at.Out](repr + ".P3") 请注意 at.Out 部分;在您的版本中,元素类型没有正确设置。使用Shapeless的一个小缺点是IDE支持存在一些问题。 - Mark Goldenstein
显示剩余5条评论

0

I. 有一个SynapseGrid函数响应式编程库。在源代码中,您可以找到一些有用的提示。

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

例如,如果我必须在SynapseGrid中实现连接,我会定义一个以下签名的方法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 
  }
}

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

II. 然而,如果您需要一次性构建所有内容,则可以返回具有构建方法的某些中间对象:

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) = ...
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接