如何使用隐式参数实现FRP?

How to implement FRP using implicit parameter?

我正在研究 Martin Odersky 的 Principles of Reactive Programming。在讲一个简单的FRP框架的实现时,他一开始就给出了一个用StackableVariable(即DynamicVairable)来跟踪当前更新的信号,这个我可以理解。但在幻灯片的最后,他提到一个更简洁的解决方案是使用隐式参数而不是 DynamicVariable。谁能告诉我如何做到这一点?

幻灯片的 link 对我不起作用。当我尝试谷歌搜索时,我现在使用 1 作为参考。

动态变量是一个线程局部变量,它保存当前评估的 Signal 的状态。这是必需的,以便 Signal 的应用方法可以访问此信息。让我们考虑以下示例代码:

val a: Signal[Int] = ???
val b: Signal[Int] = ???
val xPlusY: Signal[Int] = Signal(a() + b())

这里,当 a() 被调用时,它会将自己添加到当前评估信号的依赖项列表中。由于无法在其他任何地方访问此信息,因此我们基本上使用线程本地 a.k.a。全局变量。

这个解决方案有一些问题。例如,如果我们不在任何 Signal() 中,我们也可以调用 a()。另外,好吧,我们必须使用一个全局变量。

解决方案是通过隐式参数将此信息提供给 a():我们将签名从

Signal[T]#apply(): T

Signal[T]#apply()(implicit triggeredBy: Signal[_])

(请注意,我们可能希望使用一些封装 Signal 的新类型 TriggeredBy

这样,此方法的实现将无需 global/thread-local 变量即可访问其原始信号。

但是现在我们必须以某种方式提供这个隐含的。一种选择是同时更改信号创建函数的签名

def Signal.apply[T](fun: => T): Signal[T]

def Signal.apply[T](fun: Signal[_] => T): Signal[T]

不幸的是,那时我们的示例代码的语法必须更改,因为我们必须提供函数而不是主体:

val xPlusY: Signal[Int] = Signal { implicit triggeredBy => a() + b() }

这个问题有几个解决方案:

  • 等待隐式函数类型实现 2。这可能不会很快发生,但它将允许我们编写 Signal.apply 签名如下:

    def Signal.apply[T](fun: implicit Signal[_] => T): Signal[T]
    

    然后可以再写Signal(a() + b())

  • 使用一些宏魔法将 Signal(a() + b()) 形式的代码转换为 Signal.internalApply(implicit triggeredBy => a() + b()) 代码。这意味着 Signal.apply 现在是一个宏。这是scala.rx3走过的路,从使用的角度来看效果很好。这样也可以让我们再写Signal(a() + b())

更新:更新link 隐式函数解释到更详细的博客文章