Lud*_*son 12 monads functional-programming state-monad
我是一名JavaScript开发人员,可以提升我在函数式编程方面的技能.在管理国家时,我最近碰到了一堵墙.在搜索解决方案时,我在各种文章和视频中对状态monad进行了标记,但我很难理解它.我想知道是不是因为我希望它不是.
在Web客户端中,我从后端获取资源.为了避免不必要的流量,我在客户端创建一个包含已经获取的数据的简单缓存.缓存是我的状态.我希望我的几个模块能够保存对缓存的引用并查询它的当前状态,这个状态可能已被另一个模块修改过.
这当然不是javascript中的问题,因为它可以改变状态,但我想学习更多关于函数式编程的知识,我希望状态monad会帮助我.
我以为我可以这样做:
var state = State.of(1);
map(add(1), state);
state.evalState() // => 2
Run Code Online (Sandbox Code Playgroud)
这显然不起作用.国家总是1.
我对状态monad的假设是错误的,还是我只是错误地使用它?
我意识到我可以这样做:
var state = State.of(1);
var newState = map(add(1), state);
Run Code Online (Sandbox Code Playgroud)
...并且newState将是2的状态.但是在这里我并没有真正看到状态monad的使用,因为我将不得不创建一个新实例以便更改值.这对我来说似乎总是在函数式编程中完成,其中值是不可变的.
状态monad的目的是隐藏函数之间的状态传递.
我们来举个例子:
方法A和B需要使用一些状态并使其变异,而B需要使用A变异的状态.在具有不可变数据的函数式语言中,这是不可能的.
相反的是:初始状态与其需要的参数一起传递给A,并且A返回结果和"已修改"状态 - 实际上是新值,因为原始未被更改.这个"新"状态(也可能是结果)通过其必需的参数传递给B,并且B返回其结果和它(可能已经)修改的状态.
各地通过这个国家明确是一个PITA,所以国家单子隐藏了其下盖一元,让需要访问状态的方法来得到它通过get和set一元的方法.
为了使用有状态计算A和B,我们将它们组合成一个集合状态计算,并为该集团提供一个运行的开始状态(和参数),并返回一个最终的"修改"状态和结果(在运行A之后) ,B,以及它由其组成的任何其他东西).
从你所描述的内容来看,在我看来,你正在寻找更多类似于并发的actor模型的东西,其中状态是在一个actor中管理的,其余的代码通过它与它接口,检索(a它的非可变版本或告诉它通过消息修改.在不可变语言(如Erlang)中,actor阻塞等待消息,然后在它进入时处理一个消息,然后循环通过(尾部)递归; 它们将任何修改后的状态传递给递归调用,这就是状态被"修改"的方式.
正如你所说的那样,因为你使用的是JavaScript,所以这不是什么大问题.
我试图从 Javascript 开发人员的角度回答你的问题,因为我相信这是你的问题的原因。也许您可以在标题和标签中指定术语 Javascript。
将概念从 Haskell 转移到 Javascript 基本上是一件好事,因为 Haskell 是一种非常成熟的纯函数式语言。然而,它可能会导致混乱,就像 state monad 的情况一样。
例如,may monad 很容易理解,因为它解决了两种语言都面临的问题:由于不返回值(Javascript 中的null/ undefined)而可能出错的计算。Maybe使开发人员免于null在整个代码中进行分散检查。
在 state monad 的情况下,情况有点不同。在 Haskell 中,需要状态 monad 来组合共享可变状态的函数,而不必传递此状态。状态是一个或多个不在所涉及函数的参数中的变量。在 Javascript 中,您只需执行以下操作:
var stack = {
store: [],
push: function push(element) { this.store.push(element); return this; },
pop: function pop() { return this.store.pop(); }
}
console.log(stack.push(1).push(2).push(3).pop()); // 3 (return value of stateful computation)
console.log(stack.store); // [1, 2] (mutated, global state)
Run Code Online (Sandbox Code Playgroud)
这是所需的有状态计算,store不必在方法之间传递。乍一看,没有理由在 Javascript 中使用 state monad。但是因为store是可公开访问的,push并且会pop改变全局状态。改变全局状态是一个坏主意。这个问题可以通过多种方式解决,其中之一就是状态单子。
以下简化示例将堆栈实现为状态 monad:
function chain(mv, mf) {
return function (state) {
var r = mv(state);
return mf(r.value)(r.state);
};
}
function of(x) {
return function (state) {
return {value: x, state: state};
};
}
function push(element) {
return function (stack) {
return of(null)(stack.concat([element]));
};
}
function pop() {
return function (stack) {
return of(stack[stack.length - 1])(stack.slice(0, -1));
};
}
function runStack(seq, stack) { return seq(stack); }
function evalStack(seq, stack) { return seq(stack).value; }
function execStack(seq, stack) { return seq(stack).state; }
function add(x, y) { return x + y; }
// stateful computation is not completely evaluated (lazy evaluation)
// no state variables are passed around
var computation = chain(pop(), function (x) {
if (x < 4) {
return chain(push(4), function () {
return chain(push(5), function () {
return chain(pop(), function (y) {
return of(add(x, y));
});
});
});
} else {
return chain(pop(), function (y) {
return of(add(x, y));
});
}
});
var stack1 = [1, 2, 3],
stack2 = [1, 4, 5];
console.log(runStack(computation, stack1)); // Object {value: 8, state: Array[3]}
console.log(runStack(computation, stack2)); // Object {value: 9, state: Array[1]}
// the return values of the stateful computations
console.log(evalStack(computation, stack1)); // 8
console.log(evalStack(computation, stack2)); // 9
// the shared state within the computation has changed
console.log(execStack(computation, stack1)); // [1, 2, 4]
console.log(execStack(computation, stack2)); // [1]
// no globale state has changed
cosole.log(stack1); // [1, 2, 3]
cosole.log(stack2); // [1, 4, 5]
Run Code Online (Sandbox Code Playgroud)
可以避免嵌套函数调用。为简单起见,我省略了此功能。
Javascript 中没有任何问题可以单独使用 state monad 来解决。理解像 state monad 这样概括的东西要困难得多,它解决了所用语言中看似不存在的问题。它的使用仅仅是个人喜好的问题。