我有兴趣在我的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' } ]
然后,另一个问题,也许与样式更相关,现在我们必须已经组成了可以处理所有任务的函数,与处理数组/对象的函数相比,这些函数看起来不那么优雅,通用性较低。
[当后一个函数全部依赖于组合中初始不纯函数的返回值时,一个函数如何构成?
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?]。
引用透明的重要性在于,它允许1和programmer推理程序行为为compiler。这可以帮助证明rewrite system,简化correctness,帮助修改代码而不破坏它,或者通过algorithm,optimizing,memoization或common subexpression elimination来帮助lazy evaluation代码。
我们如何用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,您将失去所有可预测的行为。