州monad的目的是什么?

时间:2015-01-29 20:46:24

标签: functional-programming monads state-monad

我是一名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的使用,因为我必须创建一个新实例才能使值发生变化。这对我来说似乎总是在函数式编程中完成,其中值是不可变的。

4 个答案:

答案 0 :(得分:8)

状态monad的目的是隐藏函数之间的状态传递。

让我们举个例子:

方法A和B需要使用某种状态并对其进行变异,而B需要使用A变异的状态。在具有不可变数据的函数式语言中,这是不可能的。

这样做是为了:将初始状态与所需的参数一起传递给A,并且A返回一个结果和一个"修改过的"国家 - 真的是一个新的价值,因为原来没有改变。这个"新" state(也可能是结果)通过其必需的参数传递给B,B返回结果和它(可能已经)修改过的状态。

显式传递此状态是PITA,因此状态monad将其隐藏在其monadic封面下,允许需要访问状态的方法通过getset 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可公开访问,pushpop会改变全局状态。改变全球状态是一个坏主意。这个问题可以通过几种方式解决,其中一种方式正是州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/

上写了一篇文章