如何在 Javascript 中实现 Haskell 的 FRP Behavior 类型?

Posted

技术标签:

【中文标题】如何在 Javascript 中实现 Haskell 的 FRP Behavior 类型?【英文标题】:How to implement Haskell's FRP Behavior type in Javascript? 【发布时间】:2017-07-11 11:10:15 【问题描述】:

我想了解 Haskell 中函数式反应式编程的原意,以及它与 FRP 在 javascript 中的实际应用有何不同。不幸的是,我对 Haskell 的了解很肤浅,不得不坚持使用 Javascript。

这是我尝试用无类型语言实现 Haskell 的 Behavior 数据类型:

// behavior :: String -> DOMhtmlElement -> a -> (a -> Event -> a) -> (a -> b)

const behavior = type => target => x => f => 
  const destructiveSet = y => (x = f(x) (y), x), // A
   handler = event => destructiveSet(event);

  target.addEventListener(type, handler, true);
  return g => g(x);
;

A 行是必需的,因为我必须改变由调用堆栈保存的初始值x。函数体从左到右求值,返回逗号运算符,的最后一个操作数的值,即x的破坏性更新版本。

target.addEventListener 只是将给定的处理程序订阅到给定的 DOM HTML 元素。

behavior 返回一个允许对x 进行只读访问的函数。

此实现在 Javascript 中引入了只读抽象数据类型,其值仅存在于高阶函数的调用堆栈中。如果 DOM 事件仅由 GUI 用户触发,则程序无法更改 behavior 类型的值。它只能轮询它们以观察时变效应。

这种方法与 Haskell 的 Behavior 相比是否遥遥无期?

这是一个小例子 - 鼠标点击计数器(计数四秒):

const behavior = type => target => x => f => 
  const destructiveSet = y => (x = f(x) (y), x),
   handler = event => destructiveSet(event);

  target.addEventListener(type, handler, true);
  return g => g(x);
;


const comp = f => g => x => f(g(x));

const K = x => y => x;

const I = x => x;

const get = I;

const defer = n => f => x => setTimeout(f, n, x);

const log = prefix => x => console.log(prefix, x);

const inc = x => x + 1;

const counter = behavior("click") (document) (0) (comp(K) (inc));


console.log("click on the section above");

counter(get); // 0

defer(4000) (counter) (log("counted clicks:"));

【问题讨论】:

是否应该计算超过 1 次点击?无论我点击多少次,它对我来说都只计为 1。 @naomik “是否应该计算超过 1 次点击?” -- 确实如此,至少对我来说它是这样工作的(Linux 上的 Firefox 51.0.1,以防万一)。 Nitpick:在您的签名评论中,结果类型应为(a -> b) -> b 而不是a -> b 我强烈推荐阅读this paper,这对我有很大的启发,并且会告诉你一些FRP的“哲学”。它可能会让您更好地了解您的 JS 实现是否符合 FRP 的精神。据我所知,我认为它不需要大量的 Haskell 知识,只需要基础知识。 @naomik Inject functions with side-effects 【参考方案1】:

behavior 的实现更接近 FRP 术语中的事件,即使 FRP 事件和 DOM 事件没有任何共同之处。 FRP 的核心是抽象连续(相对于离散)时间的概念。 Behavior a 表示a 类型值的连续流;因此它的意义是作为一个函数从时间到价值。

Conal Eliott 是这样定义的:

μ :: Behaviour a -> (Time -> a)

那个符号是他用来描述某事物的意义的符号,它是从该事物到具体计算值的函数。

事件是“及时冻结的行为”,即它们代表在特定时刻发生的值:

μ :: Event a -> [(Time, a)]

因为 Haskell 是一种惰性语言,值流可以表示为列表,这就是上面所说的。

奇怪的是,并没有多少 FRP 的实现仍然忠实于最初的想法,因为(我认为)很难提出连续时间的高性能实现 —this C++ one seems to come close。

我鼓励您在线观看 Conal Eliott 的演讲,他是我见过的一些最优雅的 API 设计,他的解释非常清晰。

【讨论】:

以上是关于如何在 Javascript 中实现 Haskell 的 FRP Behavior 类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Wordpress 中实现 javascript

如何在javascript中实现区域/代码折叠

如何在javascript中实现分层多级数据表?

如何在 JavaScript/jQuery 中实现重载?

如何在 Javascript 中实现 Haskell 的 FRP Behavior 类型?

如何在 Javascript 中实现安全的 OAuth2 消费?