我是一名JavaScript开发人员,可以提升我在函数式编程方面的技能。在管理国家时,我最近碰到了一堵墙。在搜索解决方案时,我在各种文章和视频中对状态monad进行了标记,但我很难理解它。我想知道是不是因为我希望它不是它。
在Web客户端中,我从后端获取资源。为了避免不必要的流量,我在客户端创建一个包含已经获取的数据的简单缓存。缓存是我的状态。我希望我的几个模块能够保存对缓存的引用并查询它的当前状态,这个状态可能已被另一个模块修改过。
这当然不是javascript中的问题,因为它可以改变状态,但我想学习更多关于函数式编程的知识,我希望状态monad会帮助我。
我原以为我可以这样做:
var state = State.of(1);
map(add(1), state);
state.evalState() // => 2
这显然不起作用。国家总是1.
我对州monad的假设是错误的,还是我只是错误地使用它?
我意识到我可以这样做:
var state = State.of(1);
var newState = map(add(1), state);
...而newState
将是2的状态。但是在这里我并没有真正看到状态monad的使用,因为我必须创建一个新实例才能使值发生变化。这对我来说似乎总是在函数式编程中完成,其中值是不可变的。
答案 0 :(得分:8)
状态monad的目的是隐藏函数之间的状态传递。
让我们举个例子:
方法A和B需要使用某种状态并对其进行变异,而B需要使用A变异的状态。在具有不可变数据的函数式语言中,这是不可能的。
这样做是为了:将初始状态与所需的参数一起传递给A,并且A返回一个结果和一个"修改过的"国家 - 真的是一个新的价值,因为原来没有改变。这个"新" state(也可能是结果)通过其必需的参数传递给B,B返回的结果和它(可能已经)修改过的状态。
显式传递此状态是PITA,因此状态monad将其隐藏在其monadic封面下,允许需要访问状态的方法通过get
和set
monadic方法获取它。
为了使用有状态计算A和B,我们将它们组合成一个集合状态计算,并为该集团提供一个开始状态(和参数)来运行,并返回一个最终的"修改的"状态和结果(在通过A,B和其他任何组成的东西之后运行)。
从您所描述的内容来看,在我看来,您似乎正在寻找更多类似于actor model of concurrency的内容,其中状态是在演员和其他代码接口中管理的通过它,检索它(不可变的版本)或通过消息告诉它被修改。在不可变语言(如Erlang)中,actor阻塞等待消息,然后在它进入时处理一个消息,然后循环通过(尾部)递归;他们将任何修改后的状态传递给递归调用,这就是状态得到修改"。
正如你所说,但是,由于你使用的是JavaScript,这并不是什么大问题。
答案 1 :(得分:3)
它确实像第二个描述一样,返回一个新的不可变状态。但是,如果你这样称呼它并不是特别有用。它派上用场的方法是,如果你有一堆你想要调用的函数,每个函数都从上一步返回一个状态并返回一个新的状态,可能还有另一个值。
使它成为monad基本上允许你指定一个只列出要执行的函数名的列表,而不是一遍又一遍地重复newState = f(initialState); newNewState = g(newState); finalState = h(newNewState);
。 Haskell有一个称为do-notation的内置符号来完成这个。如何在JavaScript中完成它取决于您正在使用的函数库,但是以最简单的形式(没有任何中间结果的绑定),它可能看起来像finalState = do([f,g,h], initialState)
。
换句话说,状态monad并没有神奇地使不变性看起来像可变性,但它可以简化在某些情况下对中间状态的跟踪。
答案 2 :(得分:2)
我试图从Javascript开发人员的角度回答您的问题,因为我认为这是导致问题的原因。也许你可以在标题和标签中指定Javascript一词。
将概念从Haskell转移到Javascript基本上是件好事,因为Haskell是一种非常成熟,纯粹的功能语言。然而,它可能导致混乱,如州monad的情况。
例如,mon monad可以很容易理解,因为它处理两种语言都面临的问题:通过不返回值({J}中的null
/ undefined
)可能会出错的计算。 Maybe
可以避免开发人员在代码中散布null
次检查。
在州monad的情况下,情况有点不同。在Haskell中,状态monad是必需的,以便组成共享可变状态的函数,而不必通过这种状态。 State是一个或多个变量,不属于所涉及函数的参数。在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)
这是所需的有状态计算,store
不必从方法传递到方法。乍一看,没有理由在Javascript中使用状态monad。但由于store
可公开访问,push
和pop
会改变全局状态。改变全球状态是一个坏主意。这个问题可以通过几种方式解决,其中一种方式正是州monad。
以下简化示例将堆栈实现为状态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]
可以避免嵌套函数调用。为简单起见,我省略了此功能。
Javascript中没有任何问题可以通过状态monad单独解决。并且要理解像州monad那样概括的东西要困难得多,它解决了用过的语言中看似不存在的问题。它的使用仅仅是个人偏好的问题。
答案 3 :(得分:1)
州。在课堂上,它可能是其属性的价值。在程序中,它可能是变量的价值。在像javascript甚至java这样允许可变性的语言中,我们将状态作为参数传递给mutating函数。但是,在Haskell和Scala等不喜欢变异(称为副作用或不纯)的语言中,显式返回新状态(带有更新),然后传递给其消费者。为了隐藏这个显式的状态传递和返回,Haskell(和Scala)有了State Monad的这个概念。我在https://lakshmirajagopalan.github.io/state-monad-in-scala/
上写了一篇文章