请注意,尽管这个问题中的示例是用Javascript编码的,但基本概念在Haskell和我中都很常见,虽然我更喜欢用Javascript来表达我自己,但我也喜欢用Haskell来回答。
在Javascript中,我使用CPS根据一元原则处理异步计算。然而,为了简单起见,我将使用普通的延续单体来回答这个问题。
一旦我的延续组合增长,我就会发现自己处于需要访问这些组合的中间结果的情况下。因为Javascript是命令式的,所以很容易将这样的结果存储在变量中并在以后访问它们。但是,由于我们讨论的是延续,访问中间结果意味着调用函数并多次访问它们,这意味着大量的重新计算。
这似乎非常适合于记忆。但是,如果一个函数不返回任何东西,而只是调用它的continuation (和btw ),我如何才能记住该函数的返回值。正如我之前提到的,我使用的异步函数在Javascript的事件循环的当前循环中也不返回任何内容)。
似乎我必须提取正确的续集。也许通过shift/reset使用分隔的延续是可能的,但我不知道如何应用这些组合符。这个问题可能并不难解决,我只是被连续通过style...so这块神奇的土地搞糊涂了,请纵容我。
下面是一个在Javascript中没有记忆的Cont的简化示例:
const taggedLog = tag => s =>
(console.log(tag, s), s);
const id = x => x;
const Cont = k => ({
runCont: k,
[Symbol.toStringTag]: "Cont"
});
const contAp = tf => tx =>
Cont(k => tf.runCont(f => tx.runCont(x => k(f(x)))));
const contLiftA2 = f => tx => ty =>
contAp(contMap(f) (tx)) (ty);
const contOf = x => Cont(k => k(x));
const contMap = f => tx =>
Cont(k => tx.runCont(x => k(f(x))));
const contReset = tx => // delimited continuations
contOf(tx.runCont(id));
const contShift = f => // delimited continuations
Cont(k => f(k).runCont(id));
const inc = contMap(x => taggedLog("eval inc") (x + 1));
const inc2 = inc(contOf(2));
const inc3 = inc(contOf(3));
const add = contLiftA2(x => y => taggedLog("eval add") (x + y));
const mul = contLiftA2(x => y => taggedLog("eval mul") (x * y));
const intermediateResult = add(inc2) (inc3);
mul(intermediateResult) (intermediateResult).runCont(id);
/*
should only log four lines:
eval inc 3
eval inc 4
eval add 7
eval mul 49
*/
发布于 2019-08-02 04:49:31
您的问题似乎是您的Cont还没有monad实现。这样,访问以前的结果就非常简单了--它们只是嵌套的延续回调的作用域(作为常量):
const contChain = tx => f =>
Cont(k => tx.runCont(r => f(r).runCont(k)));
contChain( add(inc2) (inc3), intermediateResult => {
const intermediateCont = contOf(intermediateResult);
return mul(intermediateCont) (intermediateCont);
}).runCont(id);(当然这有点奇怪,你的所有函数都已经被提升了,并将Cont值作为参数-它们不应该这样做,只是简单地使用return Cont值的函数)
Haskell中的代码:
import Control.Monad.Cont
import Control.Applicative
let inc = liftA (+1)
let inc2 = inc $ return 2
let inc3 = inc $ return 3
let add = liftA2 (+)
let mul = liftA2 (*)
(`runCont` id) $ add inc2 inc3 >>= \intermediateResult ->
let intermediateCont = return intermediateResult
in mul intermediateCont intermediateCont
-- 49
{- or with do notation: -}
(`runCont` id) $ do
intermediateResult <- add inc2 inc3
let intermediateCont = return intermediateResult
mul intermediateCont intermediateCont
-- 49(我还没有使用monad transformers来产生taggedLog副作用)
发布于 2019-08-02 19:02:12
为了获得想要的行为,我似乎无法避免变得不纯洁。不过,杂质只是局部的,因为我只是用它的结果值替换了延续链。我可以在不改变程序行为的情况下做到这一点,因为这正是引用透明性所保证的。
下面是Cont构造函数的转换:
const Cont = k => ({
runCont: k,
[Symbol.toStringTag]: "Cont"
});
// becomes
const Cont = k => thisify(o => { // A
o.runCont = (res, rej) => k(x => { // B
o.runCont = l => l(x); // C
return res(x); // D
}, rej); // E
o[Symbol.toStringTag] = "Cont";
return o;
});A行中的thisify只是模拟this上下文,以便要构造的Object知道它自己。
代码行B是决定性的变化:在使用x (< Task >d15B>)调用res之前,我构造了另一个lambda,它将结果代码包装在当前Task对象(C)的runTask属性下,而不是仅仅将res传递给延续代码。
在出现错误的情况下,像往常一样(E)将rej应用于x。
下面是上面的运行示例,现在可以正常运行了:
const taggedLog = pre => s =>
(console.log(pre, s), s);
const id = x => x;
const thisify = f => f({}); // mimics this context
const Cont = k => thisify(o => {
o.runCont = (res, rej) => k(x => {
o.runCont = l => l(x);
return res(x);
}, rej);
o[Symbol.toStringTag] = "Cont";
return o;
});
const contAp = tf => tx =>
Cont(k => tf.runCont(f => tx.runCont(x => k(f(x)))));
const contLiftA2 = f => tx => ty =>
contAp(contMap(f) (tx)) (ty);
const contOf = x => Cont(k => k(x));
const contMap = f => tx =>
Cont(k => tx.runCont(x => k(f(x))));
const inc = contMap(x => taggedLog("eval inc") (x + 1));
const inc2 = inc(contOf(2));
const inc3 = inc(contOf(3));
const add = contLiftA2(x => y => taggedLog("eval add") (x + y));
const mul = contLiftA2(x => y => taggedLog("eval mul") (x * y));
const intermediateResult = add(inc2) (inc3);
mul(intermediateResult) (intermediateResult).runCont(id);
/* should merely log
eval inc 3
eval inc 4
eval add 7
eval add 49
*/
https://stackoverflow.com/questions/57316172
复制相似问题