But why would you EVER want to do this if there is already a continuation monad implementation in the language itself that:
is implemented natively (and better)
has all the utility functions you'd expect
has syntax constructs that allow 'do notation' without all the noise
are used by most 3rd party libraries that do something asynchronous
all in all causes less syntactic noise
Yes, I'm talking about Promises. Yes, Promises are effectively continuation monads (not to the strictest definition of a monad but neither is the implementation in this post). The .then function is the monadic bind (e.g. '>>=') and Promise.resolve is 'pure' (which you don't need as often because .then will automatically perform a pure of a value that isn't a monad)
Here is a code example showing how they're methodologically equivalent (and qualitatively better) than what is described in the post. I'm doing this in the hopes that I never have to see anybody doing this in actual code I have to work with. Stop trying to be smart. Don't reinvent the wheel. Please...
asyncfunctiondbVerifyUser(username,password){}asyncfunctiondbGetRoles(username){}asyncfunctiondbLogAccess(username){}// 'do notation'asyncfunctionverifyUser(username,password){constuserInfo=awaitdbVerifyUser(username,password);constroles=awaitdbGetRoles(username);awaitdbLogAcess(username);return{userInfo,roles};}// 'continuation monad' with nested binds (like in the post)constverifyUser=(username,password)=>dbVerifyUser(username,password).then((userInfo)=>dbGetRoles(username).then((roles)=>dbLogAccess(username).then((_)=>({userInfo,roles}))));// not to mention Promise.all if our db queries don't have to be sequential...constverifyUser=(username,password)=>Promise.all([dbVerifyUser(username,password),dbGetRoles(username),dbLogAccess(username),]).then(([userInfo,roles])=>({userInfo,roles}))// usageverifyUser('user','pass').then(({userInfo,roles})=>/*...*/).catch(handleError);//orasyncfunctionmain(){try{const{userInfo,roles}=awaitverifyUser('user','pass');// ...}catch(err){// handle error}}
The ways in which promises do not form a monad (and in fact not even pure values) are actually pretty well understood. In particular, then is not a lawful bind, and join is impossible to express (because simply referring to a value causes effects to start happening).
Moreover you cannot generally express interaction with traversable containers, functor composition, monad transformers, or other general purpose abstractions with respect to promises, because again, they don't actually form a monad.
Regarding "neither is the implementation in this post": I'm not sure you've actually grasped the content of the post if this is the conclusion you've arrived at. The operations given in this post precisely form a monad (including obeying all the relevant laws).
In the construction above, verifyUser is a pure, continuation returning function. In your snippet, async function verifyUser(user, password) { ... } is not even really a function in the functional programming sense of the word.
As a very simple example, the promise produced by mapping a Promise-based implementation of deleteUser over an array of usernames and taking the first element doesn't represent deleting the first user; instead every user in the database would be deleted. Conversely, doing the same thing with a deleteUser based on a lawful asynchronicity monad, as given in the post, would be no different than taking the first element and then applying deleteUser to it. Both would produce a continuation representing deleting the first user (nothing would actually start happening "behind the scenes").
I'm not going to argue about strict definitions with you because you don't know what you're talking about and are opinionated about your misconceptions. I simply don't have the time.
They are the same - please inform yourself more thoroughly.
// pure :: a -> m aconstpure=v=>Promise.resolve(v);// (>>=) :: m a -> (a -> m b) -> m b constbind=ma=>a2mb=>ma.then(a2mb);bind(pure(1))((v)=>pure(v+1))// == pure(2)
Not being able to implement join has nothing to do with it being a monad. It's because they're automatically flattened. You don't have to implement m (m a) -> m a if m (m a) is equivalent to m a. But again - it has nothing to do with it being a monad.
My point is that they can be used the same way as your continuation monad implementation and as such should be favoured over it because they're standard language constructs. Period.
Also, of course async function verifyUser(user, password) { ... } is a pure function. It's referentially transparent in the sense that given the same parameters the Promise returned will always be the same. How that promise is consumed doesn't matter. Again - inform yourself.
Lazy evaluation also doesn't have anything to do with purity or it being a monad. (regarding your deleteUser example. You're mixing up concepts that you don't seem to understand)
Thanks, the definition you posted above is helpful. Try evaluating const map = f => bind(x => pure(f(x))); map(pure)(pure(5)) to understand why this is not actually a lawful implementation of bind.
Without having a join operation (which can be recovered as bind(id) from a lawful bind), it's actually meaningless to talk about a "monad". Monads are fundamentally defined by an associative join and an idempotent pure, together forming a monoid.
This isn't about lazy evaluation vs strict evaluation, but rather about pure vs impure evaluation. The term verifyUser(user, password) does not purely evaluate to a representation of an effect; instead it immediately starts performing effects in the course of its evaluation. The result of evaluating it is not dependent only on its inputs, but also on the state of the world.
This means verifyUser isn't actually a function in the functional programming sense of the word, preventing us from reasoning equationally in programs that involve it. For example the following program:
const userDetails = b ? map(just)(verifyUser(user, password)) : pure(nothing)
when using promises. It is when using a lawful asynchronicity monad (e.g. the continuation monad above). Whether this is bad or good depends on whether you prefer an imperative or functional style of reasoning.
Your definition of monads is wrong. It has nothing to do with join, their time of evaluation or 'imperative vs functional reasoning' lol.
Here are the monadic laws proven with the Promise definitions from above - in js.
// pure :: a -> m aconstpure=v=>Promise.resolve(v);// (>>=) :: m a -> (a -> m b) -> m b constbind=ma=>a2mb=>ma.then(a2mb);// monadic laws// 1. left identity - pure a >>= f ≡ f aconstf=v=>pure(v+1);bind(pure(1))(f)// == f(1) ✔// 2. right identity - m >>= pure ≡ mconstm=pure(1);bind(m)(pure)// == m ✔// 3. associativity - (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)constm=pure(1);constf=v=>pure(v+1);constg=v=>pure(v*2);bind(bind(m)(f))(g)// == pure(4) ✔bind(m)(x=>bind(f(x))(g))// == pure(4) ✔
You're also wrong about the fact that the promises don't evaluate to the representation of an effect first. Of course they do. The point in time the underlying implementation decides to consume that value has no significance whatsoever. As I said - you're mixing up concepts, don't understand monads and likely don't understand Promises either.
This is the first time I've heard that monads have nothing to do with the join operation. You should share this revolutionary insight with the mathematics community.
Regarding the "proof" of the monadic laws above, unfortunately the laws don't hold for the definitions given (the proof-by-single-example notwithstanding). In fact, the definitions are not even well-typed.
Conveniently, to disprove something requires only a single counterexample:
// Function composition// :: a -> aconstid=x=>x// :: (b -> c) -> (a -> b) -> a -> cconstcompose=f=>g=>x=>f(g(x))// A pair of operations witnessing that a particular type constructor forms a monad// :: type Monad m = { pure: a -> m a, bind: m a -> (a -> m b) -> m b }// The associativity law satisfied by any monad// :: Monad m -> [m Int, m Int]consttestAssociativity=({pure,bind})=>{// Some selected inputs// :: m Intconstmx=pure(42)// :: a -> m (m a)constf=compose(pure)(pure)// :: m a -> m aconstg=ma=>bind(ma)(pure)// associativity:// (mx >>= f) >>= g// ===// mx >>= \x -> f x >>= g// :: m Intconstml=bind(bind(mx)(f))(g)// :: m Intconstmr=bind(mx)(x=>bind(f(x))(g))return[ml,mr]}// The array monad// :: Monad Arrayconstarray={pure:v=>[v],bind:ma=>a2mb=>ma.reduce((p,a)=>[...p,...a2mb(a)],[])}// Is it really a monad?const[a1,a2]=testAssociativity(array)console.log(a1)console.log(a2)// The promise "monad"// :: Monad Promiseconstpromise={pure:v=>Promise.resolve(v),bind:ma=>a2mb=>ma.then(a2mb)}// Is it really a monad?const[p1,p2]=testAssociativity(promise)p1.then(x=>{console.log(x)})p2.then(x=>{console.log(x)})
I'd like to have discussed how the word "monad" refers to a particular kind of endofunctor with join and pure natural transformations, but I really have to take a break from this conversation. I don't mind discussing things with people I disagree with, but the complete lack of manners displayed in your comments goes poorly with your total ignorance of the subject.
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
But why would you EVER want to do this if there is already a continuation monad implementation in the language itself that:
Yes, I'm talking about Promises. Yes, Promises are effectively continuation monads (not to the strictest definition of a monad but neither is the implementation in this post). The .then function is the monadic bind (e.g. '>>=') and Promise.resolve is 'pure' (which you don't need as often because .then will automatically perform a pure of a value that isn't a monad)
Here is a code example showing how they're methodologically equivalent (and qualitatively better) than what is described in the post. I'm doing this in the hopes that I never have to see anybody doing this in actual code I have to work with. Stop trying to be smart. Don't reinvent the wheel. Please...
The ways in which promises do not form a monad (and in fact not even pure values) are actually pretty well understood. In particular,
then
is not a lawfulbind
, andjoin
is impossible to express (because simply referring to a value causes effects to start happening).Moreover you cannot generally express interaction with traversable containers, functor composition, monad transformers, or other general purpose abstractions with respect to promises, because again, they don't actually form a monad.
Regarding "neither is the implementation in this post": I'm not sure you've actually grasped the content of the post if this is the conclusion you've arrived at. The operations given in this post precisely form a monad (including obeying all the relevant laws).
In the construction above,
verifyUser
is a pure, continuation returning function. In your snippet,async function verifyUser(user, password) { ... }
is not even really a function in the functional programming sense of the word.As a very simple example, the promise produced by mapping a Promise-based implementation of
deleteUser
over an array of usernames and taking the first element doesn't represent deleting the first user; instead every user in the database would be deleted. Conversely, doing the same thing with adeleteUser
based on a lawful asynchronicity monad, as given in the post, would be no different than taking the first element and then applyingdeleteUser
to it. Both would produce a continuation representing deleting the first user (nothing would actually start happening "behind the scenes").I'm not going to argue about strict definitions with you because you don't know what you're talking about and are opinionated about your misconceptions. I simply don't have the time.
They are the same - please inform yourself more thoroughly.
Not being able to implement
join
has nothing to do with it being a monad. It's because they're automatically flattened. You don't have to implementm (m a) -> m a
ifm (m a)
is equivalent tom a
. But again - it has nothing to do with it being a monad.My point is that they can be used the same way as your continuation monad implementation and as such should be favoured over it because they're standard language constructs. Period.
Also, of course
async function verifyUser(user, password) { ... }
is a pure function. It's referentially transparent in the sense that given the same parameters the Promise returned will always be the same. How that promise is consumed doesn't matter. Again - inform yourself.Lazy evaluation also doesn't have anything to do with purity or it being a monad. (regarding your
deleteUser
example. You're mixing up concepts that you don't seem to understand)Thanks, the definition you posted above is helpful. Try evaluating
const map = f => bind(x => pure(f(x))); map(pure)(pure(5))
to understand why this is not actually a lawful implementation ofbind
.Without having a
join
operation (which can be recovered asbind(id)
from a lawfulbind
), it's actually meaningless to talk about a "monad". Monads are fundamentally defined by an associativejoin
and an idempotentpure
, together forming a monoid.This isn't about lazy evaluation vs strict evaluation, but rather about pure vs impure evaluation. The term
verifyUser(user, password)
does not purely evaluate to a representation of an effect; instead it immediately starts performing effects in the course of its evaluation. The result of evaluating it is not dependent only on its inputs, but also on the state of the world.This means
verifyUser
isn't actually a function in the functional programming sense of the word, preventing us from reasoning equationally in programs that involve it. For example the following program:is not the same program as:
when using promises. It is when using a lawful asynchronicity monad (e.g. the continuation monad above). Whether this is bad or good depends on whether you prefer an imperative or functional style of reasoning.
Your definition of monads is wrong. It has nothing to do with
join
, their time of evaluation or 'imperative vs functional reasoning' lol.Here are the monadic laws proven with the Promise definitions from above - in js.
You're also wrong about the fact that the promises don't evaluate to the representation of an effect first. Of course they do. The point in time the underlying implementation decides to consume that value has no significance whatsoever. As I said - you're mixing up concepts, don't understand monads and likely don't understand Promises either.
This is the first time I've heard that monads have nothing to do with the join operation. You should share this revolutionary insight with the mathematics community.
Regarding the "proof" of the monadic laws above, unfortunately the laws don't hold for the definitions given (the proof-by-single-example notwithstanding). In fact, the definitions are not even well-typed.
Conveniently, to disprove something requires only a single counterexample:
I'd like to have discussed how the word "monad" refers to a particular kind of endofunctor with
join
andpure
natural transformations, but I really have to take a break from this conversation. I don't mind discussing things with people I disagree with, but the complete lack of manners displayed in your comments goes poorly with your total ignorance of the subject.