IO是组成链中的第一位

问题描述 投票:0回答:2

我有兴趣在我的JavaScript函数组合中尝试类似Haskell的IO monad。

Folktale具有Task之类的东西似乎与Haskell的IO类似,因为它是惰性的,因此在技术上是纯净的。它代表将来可能发生的动作。但是我有几个问题。

当后一个函数全部依赖于组合中初始不纯函数的返回值时,一个函数如何构成一个组合?必须先运行实际的Task,然后将返回的数据隐式传递到进一步的函数中。一个人不能仅仅传递一个未解决的任务来做任何有用的事情,还是可以?它看起来像。

compose(doSomethingWithData, getDataFromServer.run());

我可能缺少关键的东西,但是这样做有什么好处?

一个相关的问题是,对不纯函数的惰性评估具有什么特定的优势?当然,它提供了引用透明性,但是理解问题的核心是不纯函数返回的数据结构。管道传输数据的所有后面的功能都取决于数据。那么不纯函数的引用透明性如何使我们受益?

EDIT:因此,在查看了一些答案之后,我能够通过链接轻松地编写任务,但是我更喜欢使用compose函数的人机工程学。这可行,但是想知道对于函数式程序员来说这是否完全是惯用的:

const getNames = () =>
  task(res =>
    setTimeout(() => {
      return res.resolve([{ last: "cohen" }, { last: "kustanowitz" }]);
    }, 1500)
);

const addName = tsk => {
  return tsk.chain(names =>
    task(resolver => {
      const nms = [...names];
      nms.push({ last: "bar" });
      resolver.resolve(nms);
    })
  );
};
const f = compose(
  addName,
  getNames
);

const data = await f()
  .run()
  .promise();
// [ { last: 'cohen' }, { last: 'kustanowitz' }, { last: 'bar' } ]

然后,另一个问题,也许与样式更相关,现在我们必须已经组成了可以处理所有任务的函数,与处理数组/对象的函数相比,这些函数看起来不那么优雅,通用性较低。

javascript functional-programming monads folktale
2个回答
1
投票

[当后一个函数全部依赖于组合中初始不纯函数的返回值时,一个函数如何构成?

chain方法用于合成单子。考虑以下裸露的骨头chain示例。

Task

必须先运行实际的Task,然后将返回的数据隐式传递到进一步的函数中。

正确。但是,可以推迟执行任务。

一个人不能只是传递一个未解决的任务来做任何有用的事情,或者可以吗?

如上所述,您确实可以使用// Task :: ((a -> Unit) -> Unit) -> Task a const Task = runTask => ({ constructor: Task, runTask, chain: next => Task(callback => runTask(value => next(value).runTask(callback))) }); // sleep :: Int -> Task Int const sleep = ms => Task(callback => { setTimeout(start => { callback(Date.now() - start); }, ms, Date.now()); }); // main :: Task Int const main = sleep(5000).chain(delay => { console.log("%d seconds later....", delay / 1000); return sleep(5000); }); // main is only executed when we call runTask main.runTask(delay => { console.log("%d more seconds later....", delay / 1000); });方法来组成尚未开始的任务。

一个相关的问题是对不纯函数的惰性评估具有什么特定优势?

这是一个非常广泛的问题。也许以下SO问题可能会让您感兴趣。

chain

那么不纯函数的引用透明性如何使我们受益?

引用维基百科[[What's so bad about Lazy I/O?]

引用透明的重要性在于,它允许1programmer推理程序行为为compiler。这可以帮助证明rewrite system,简化correctness,帮助修改代码而不破坏它,或者通过algorithmoptimizingmemoizationcommon subexpression elimination来帮助lazy evaluation代码。


0
投票

我们如何用Java语言表达Haskell的IO类型?实际上我们不能,因为在Haskell IO中,IO是一种非常特殊的类型,与运行时密切相关。我们可以用Javascript模仿的唯一属性是对显式thunk的延迟评估:

parallelization

通常,懒惰的评估伴随着共享,但是为了方便起见,我不再赘述。

现在您将如何构成这样的类型?好吧,您需要在重音环境中编写它。懒散地编排的唯一方法是嵌套它们,而不是立即调用它们。因此,您不能使用仅提供功能的函数实例的函数组合。您需要const Defer = thunk => ({ get runDefer() {return thunk()} })); 的适用实例(ap / of)和monad(chain)实例来链接或嵌套它们。

应用程序和monad的基本特征是您无法逃避它们的上下文,即,一旦计算结果进入应用程序/ monad内,就不能再对其进行拆包。*所有后续计算都必须在内部进行各自的背景。正如我已经在Defer中提到的那样,上下文是笨拙的。

因此,最终,当您使用Defer / ap组成thunk时,您将构建一个嵌套的延迟函数调用树,仅当您调用外部thunk的chain时才进行评估。

这意味着直到第一个runDefer调用之前,您的thunk组成或链接都保持纯净。这是我们所有人都应该追求的非常有用的属性。


**您当然可以用Java逃脱monad,但现在它不再是monad,您将失去所有可预测的行为。

© www.soinside.com 2019 - 2024. All rights reserved.