3 ms·
Well lets see if the first law holds. It says Promise.resolve(a).then(f) == f(a) Immediately this is not true, since `f` can throw or return a non-promise. B
by spion 10y ago
Well lets see if the first law holds. It says
Promise.resolve(a).then(f) == f(a)
Immediately this is not true, since `f` can throw or return a non-promise.
But lets say we restrict the type of f so that it can only return promises. Once again, the statement is not correct - the executed side effect is not the same. The left side will execute with one tick delay of the microtask queue, while on the right side the side effect executes immediately. So the side effects are not equivalent. If we write `f(a); f(b);` and `Promise.resolve(a).then(f); f(b);`, in the first case the side effects will start in order; in the second case, they will start in the reverse order. If we cannot replace one side of equal sign in the law with the other and have things run exactly the same, that means equational reasoning doesnt work [1], and the law doesn't apply.
In theory you could probably make monads with side-effects, but promises don't satisfy the laws. Its much easier if the monad only represents side effects but doesn't actually run them. You can think of them as redux action objects - they don't actually do anything once they are created. They only represent the side effect that will be run. For example, their internal representation could be {action: 'readFile', argument: 'path/to/file.json'}. ($)
You can construct actions from other actions by chaining them with a function that will take the result from the action and produce a new action object. Those would be derived actions. You can think of them as cons cells - they have the original action, and the function that produces a new action. Example representation: {originalAction: {action: 'readFile', argument: 'path/to/file.json'}, nextActionFunction: result => printLine(result)}
Finally, you pass the action to the "interpreter". The interpreter reads each action, runs the originalAction part of the cons cell, then produces a new actioin using the function in the nextActionFunction cell. It repeats that until it gets to an empty nextActionFunction cell.
($) Anoter practical representation for compile-to-js languages would be to use a thunk for the base (original) action. A thunk is a function without arguments, that when run, will produce a side-effect and return a value. The interpreter would then be simply a thunk runner. An async thunk can also be used, and thats a thunk that takes a callback that will be called with the eventual value. Still, we're only producing values without creating side effects: the laws are much easier to uphold.
[1]: http://www.haskellforall.com/2013/12/equational-reasoning.html http://www.haskellforall.com/2013/12/equational-reasoning.ht...
- zeahfj 10y agoHow would it be less of a Monad than the IO Monad? That's the part I'm not getting. It would make sense to me if neither were considered monads.
- spion 10y agoIf the monad laws don't hold, its not a monad. I demonstrated the first law not holding, therefore promises are not a monad.
- azernik 10y agoThe first one is a pretty nitpicking one, IMO; all that's happened is that then/fmap and map have been collapsed into a single function that dynamically dispatches on the type of the returned value to decide which behavior to execute. More of an ergonomic convenience for programmers who don't quite grasp the distinction. Edit: And with regard to the side effect ordering question -monads are only well-known defined for pure functions anyway! By that standard, any Scala code using non-pure functions is also not using a monad. This I inevitable in a language that does not enforce purity, which is basically every widely-adopted one that's not Haskell. A definition of "support" that excludes implementations that allow (but do not require) non-pure functions is not a very useful definition.
- spion 10y agoThats not the issue here. Even if the functions themselves are pure (beyond the act of creating a promise), the strictness of promises together with their micro-task queue rule impose execution ordering that make them a non-monad. So its impossible to write your functions in a way that the laws hold. And if you can't use the laws for equational reasoning, then what good is it whether its a "monad" or not anyway?