I'm trying to answer your question from the perspective of a Javascript developer, because I believe that this is the cause of your problem. Maybe you can specify the term Javascript in the headline and in the tags.
Transferring of concepts from Haskell to Javascript is basically a good thing, because Haskell is a very mature, purely functional language. It can, however, lead to confusion, as in the case of the state monad.
The maybe monad for instance can be easily understood, because it deals with a problem that both languages are facing: Computations that might go wrong by not returning a value (null
/undefined
in Javascript). Maybe
saves developers from scattering null
checks throughout their code.
In the case of the state monad the situation is a little different. In Haskell, the state monad is required in order to compose functions, which share changeable state, without having to pass this state around. State is one or more variables that are not among the arguments of the functions involved. In Javascript you can just do the following:
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)
This is the desired stateful computation and store
does not have to be passed around from method to method. At first sight there is no reason to use the state monad in Javascript. But since store
is publicly accessible, push
and pop
mutate global state. Mutating global state is a bad idea. This problem can be solved in several ways, one of which is precisely the state monad.
The following simplified example implements a stack as state 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]
The nested function calls could be avoided. I've omitted this feature for simplicity.
There is no issue in Javascript that can be solved solely with the state monad. And it is much harder to understand something as generalized as the state monad, that solves a seemingly non-existing problem in the used language. Its use is merely a matter of personal preference.