promises-aplus / promises-spec

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.
https://promisesaplus.com/
Creative Commons Zero v1.0 Universal
1.84k stars 164 forks source link

Incorporate monads and category theory #94

Closed paulmillr closed 11 years ago

paulmillr commented 11 years ago

Brian Mckenna criticised current spec. He proposes to use FP approach to achieve much better modularity.

Suggest to read it, really good ideas with just three changes.

http://brianmckenna.org/blog/category_theory_promisesaplus

His proposal is to incorporate into spec three simple apis:

  1. Promise.of(a) will turn anything into promise.
  2. Promise#then(f) should take one function, not two.
  3. Promise#onRejected(f): move onRejected to prototype instead of second arg.

edit: see https://github.com/promises-aplus/resolvers-spec/issues/24 for more discussion

bergus commented 11 years ago

Yes, I really would like monadic promises as well…

Only I fear that won't happen. I already objected in #75 on the recursive assimilation process, but as it stands the then method is overloaded with join as well :-(

And that point function is subject of the (independent) resolvers-spec. As Brian already wrote, it would be sufficient in most cases to construct a simple thenable, instead of using some implementation-dependent Promise constructor.

domenic commented 11 years ago

Yeah this is really not happening. It totally ignores reality in favor of typed-language fantasy land, making a more awkward and less useful API just to satisfy some peoples' aesthetic preferences that aren't even applicable to JavaScript. It misses the point of promises (modeling synchronous control flow from imperative languages), albeit in a novel way from the usual misunderstandings.

It is also hilariously inaccurate, as the thenable described comes nowhere near satisfying the spec. My guess is that it would pass approximately one of the ~500 tests in our test suite.

Someone more diplomatic than me should probably chime in too.

briancavalier commented 11 years ago

@paulmillr, @bergus: I fully understand and appreciate this perspective, and similar ideas have been discussed at various lengths by the Promises/A+ contributors.

As @domenic has rightly pointed out, though, it isn't practical for Promises/A+ to go in the direction Brian Mckenna is proposing.

puffnfresh commented 11 years ago

@domenic you miss the point of monads. They implement exactly what you're describing in the post. They create sequential, synchronous control flow. That's why they're used in Haskell as IO! That's why promises are monadic!

Please point out where I'm missing the point of promises.

I'm basing the API off of category theory but it's for my aesthetic preferences!? When you say the API is "more awkward and less useful", that's reflecting your aesthetic preferences. Mine haven't even come into it!

I'm going to work on a separate specification - I'm hoping the rest of the JavaScript community will embrace it, rather than immediately reject it because it was based off of types.

juandopazo commented 11 years ago

I'm going to work on a separate specification

@pufuwozu terrible idea. The reason why A+ isn't separating map and bind isn't because of aesthetic preferences. We didn't even discuss the issue because it's a breaking change and it came way too late into the discussion. then became a de facto standard that even the DOM is now likely to follow with DOMFuture.

puffnfresh commented 11 years ago

@juandopazo I don't really care about having bind fall back to map when it doesn't return a promise. I'd prefer it to throw an exception but I'll just be careful.

What I do care about is a specified way to create promises and onRejected to be separate from then. That's all.

ForbesLindesay commented 11 years ago

A specified way to create promises is coming, it's just not here yet. see https://github.com/promises-aplus/resolvers-spec, which is nearly ready for a version 0.

ForbesLindesay commented 11 years ago

Separating .then and .onRejected was not done for very good reasons:

  1. It allows for the parallel control flow of: Do Operation A, If operation A succeeded, do B, if operation A failed, do C. Separation makes that very hard to write.
  2. Keeping the API surface that needed to be implemented for interoperability to a minimum is critical to the success of promises. There are are many promises out there that don't come close to implementing this spec, but are still good enough for full interoperability because they have a .then method that takes two arguments: onFulfilled and onRejected.
briancavalier commented 11 years ago

What I do care about is a specified way to create promises and onRejected to be separate from then. That's all.

@pufuwozu In that case, I would suggest you should join the discussion for creating promises rather than go out on your own. You could also propose a discussion to this group for an optional onRejected API (several promise implementations provide this already: when.js's promise.otherwise, Q's promise.catch, other libs provide promise.fail, etc.).

However, changing then simply is not an option.

juandopazo commented 11 years ago

While it might be nice for some people to have a specified way of creating promises, it's certainly unnecessary. Different platforms will have different ways of creating them. This is already the case. For instance, see the WinJS promises vs DOMFuture. The point of having a spec is compatibility between them. And in that spirit, having a smaller API surface is better.

Creating a new specification just to add onRejected sounds a bit overkill.

ForbesLindesay commented 11 years ago

Finally, the reason we didn't separate out bind and map is simply that it's not pragmatic to do so. It fits very nice with a theoretical computer science model of the world, but not with practical usage of the libraries. JavaScript is characterized by pragmatism over theory, and is ultimately devoid of type safety as a result. Without the benefits of type safety, separating out bind and map doesn't gain you anything.

It's not the aesthetics that @domenic or me or @briancavalier or anyone else like that matter; it's the aesthetics of the language itself. JavaScript's concept of a promise is subtly different to (although closely related to) the idea of a monad. It's the result of people attempting to use the ideas of a monad in real world applications, not the result of ignoring past work and coming up with something new from scratch.

puffnfresh commented 11 years ago

@ForbesLindesay I don't care very much about the separation of bind and map. As long as it's at least bind then I'll just be careful with the other behaviour.

... ideas of a monad in real world applications...

That's exactly what I want. Let's not implement partial monads that we can't abstract over. Let's recognise it for what it is and allow functions which work for ALL monads. Why not allow DRY code instead of some strange idea of aesthetics of JavaScript?

It might be too late but I'd rather a specification which allows DRY code with very partial backwards compatibility. I do not accept aesthetics of JavaScript as an excuse for developers to write more libraries that we can't abstract over.

ForbesLindesay commented 11 years ago

We can abstract over them. There are hundreds of JavaScript libraries that use the promises and next to none that use traditional monad APIs.

You completely (deliberately?) missed the point of what I was saying about monads in real world applications. I'm saying people took the ideas of monads and used them in real world applications and adapted them to fit well with how JavaScript works. They are not, nor are they designed to be, interoperable with monads from Haskell. If you're creating a bridge for interop between the two languages, you could always automate that conversion.

They are very demonstrably DRY and possible to abstract over. If anything the .then method allows for more DRY code because you don't have to special case for whether the result is a promise or a non-promise value. Functions like Q.all demonstrate that they are a sufficiently powerful abstraction.

This is beginning to look like trolling, and if it continues to look like trolling, I will just be muting this thread. Without any interest int he aesthetics of JavaScript, I don't see your competing spec as anything likely to cause a problem.

juandopazo commented 11 years ago

I don't see how you can't abstract over promises. You showed exactly how in your post. If we followed the DOMFuture API, it would look like this:

// the DOMFuture way of defining point()
function point(value) {
  return new Future(function (resolver) {
    resolver.accept(value);
  });
}
// Adding onRejected
Future.prototype.onRejected = function (f) {
  return this.then(null, f);
};

// flatMap is exactly the same if you don't want to separate bind and map
function flatMap(p, f) {
    return p.then(f);
}

Starting a standards war over this sounds like a great way to use our time.

puffnfresh commented 11 years ago

People misunderstood what I meant by "abstract over" - of course we can build libraries on top of the promises specification :)

I want to abstract over all monads. That's a very important thing for DRY code. I should be able to write code like this for example:

function liftA2(pa, pb, f) {
    return ap(pb, map(pa, function(a) {
        return function(b) {
            return f(a, b);
        };
    }));
}

// Promises (called when both succeed)
liftA2(readFIle('hello.txt'), readFile('world.txt'), function(hello, world) {
    console.log(hello + ' ' + world);
});

// Optional values (only inserted into database when both exist)
liftA2(optionalUsername, optionalPassword, function(username, password) {
    insertIntoTable("user", {username: username, password: password});
});

// Arrays (function called for each element of each array)
liftA2([1, 2, 3], [4, 5, 6], function(a, b) {
    console.log(a + b);
});

All I need to make the above work is for optional values and arrays to have a then and each constructor to have a point function. I don't have to write that function for each different monad - I only have to write it once. That's just one example of a function that works for any monad - there's many, many more.

I do care about the aesthetics for the API. I don't not accept aesthetics of JavaScript as an excuse to not allow me to do the above.

I care very much about generalised, DRY code. It's the right thing to do, even in JavaScript.

Twisol commented 11 years ago

It allows for the parallel control flow of: Do Operation A, If operation A succeeded, do B, if operation A failed, do C. Separation makes that very hard to write.

It's not just hard, it's impossible to get the same semantics. Promise has to be a bifunctor (i.e. has a bimap that takes two functions) for this to work at all. There are other bifunctors out there, such as Either, with either as its bimap.

@pufuwozu: I think the reason you're getting so much pushback on this, is that we create Promise APIs to make it easier for developers to write and manage asynchronous code. From my own experimentation in this area, you can only take the formalization so far in Javascript before the language starts to push back. The fmap/bind divide is one of those places; it's just much easier to use the library if you combine the two together.

You recall, perhaps, how electromagnetism and the weak force are actually part of the same mechanism, but only at very high temperatures? Well, we simply can't turn up the furnace that high in Javascript. Certain formalisms work beautifully in Haskell but fall flat in Javascript without support from the language. Haskell's static typing lets you offload a ton of work onto the compiler, but Javascript doesn't help much at all!

Prime example: in my own sandbox I've attempted to split Promise into two parts: a Result wrapping of try-catch, and a Future for deferred return values. It's theoretically nice until you realize one thing: monads don't naïvely compose. I don't know how the heck you would write monad transformers in Javascript. :grinning:

puffnfresh commented 11 years ago

@Twisol JavaScript is limited but we can still achieve DRY code by applying category theory. I do it: http://bilby.brianmckenna.org/ - the "but real world" excuse doesn't apply; I've used that library in a compiler!

Different monads don't compose but monad transformers are easy to implement in JS when you get to them ;)

juandopazo commented 11 years ago

@pufuwozu can you show with a concrete example why you can't write the example code you posted with promises?

ForbesLindesay commented 11 years ago

Your example of code that would be difficult to write with promises as they currently stand would in fact be easy to write for promises. I'm not 100% sure of the behavior of ap and map since you haven't included source code for them, but those two functions would (obviously) be different for promises vs. traditional monads. Since there are vastly more libraries using promises than monads, it is the monads that are preventing you from writing DRY code.

If you provide JavaScript source for ap and map using monads, I will write the corresponding functions for promises. Otherwise, stop trolling.

kennknowles commented 11 years ago

+1 to not starting a standards war. Any correct model of promises can be wrapped into a monadic interface, and it is pretty normal for standards to be a little off and require wrapping with a library.

Also, our understanding will improve over time, and it would be a disaster to have to move as slow as standards processes and have to wait for social trends to fall in line with mathematical truths. For example, before 2008 @pufuwozu would probably not have known to mention applicative functors.

-1 to the theory-phobia and accusations of trolling. Whether you like it or not, promises form a monad, and @pufuwozu is advocating that the standard expose this very useful structure. I understand why it will not, and agree somewhat with the decision, but that does not reduce the validity of the criticism.

puffnfresh commented 11 years ago

@ForbesLindesay you should take a look at my blog post:

function map(p, f) {
    return flatMap(p, function(a) {
        return point(f(a));
    });
}

function ap(p, pf) {
    return flatMap(pf, function(f) {
        return map(p, f);
    });
}

So we need a both a point and flatMap which works for Arrays, optional values, promises and anything else monadic. Then we can derive lots of useful DRY functions, like the liftA2 function that I posted above.

There will be no specified way to do this. That's what my blog post should have shown.

Thanks @kennknowles.

Twisol commented 11 years ago

Leaving aside the issue of the spec and whether or not we should change it (my opinion: we shouldn't), I'd be interested in seeing a pure approach to Promises. Something doesn't have to conform to a spec to be useful, and if it turned out that we're all wrong - that you can write pure code without it being brittle in the face of dynamic typing - then there's no reason inspiration couldn't be taken from the approach.

I don't want to see a standards war or any division of effort; just understand that there are reason that the spec is the way it is, and we need more than an impassioned plea for purity to make changes.

ForbesLindesay commented 11 years ago

To clarify:

I'm not anti type theory per say. I actually find it extremely interesting. I've written a fair bit of code in languages like ML and F# that do a decent job of strong typing systems. I also think it would be an interesting experiment to attempt to create a strongly typed subset of JavaScript.

This is not the place for such experiments though, and my accusations of trolling are not directed at @paulmillr who made the very reasonable comment that we should consider category theory and monads more extensively. My accusation of trolling was leveled at @pufuwozu because he repeatedly tells us that the promises spec as it stands is insufficient for writing certain functions in a DRY way, yet refuses to demonstrate any actual problem.

puffnfresh commented 11 years ago

@ForbesLindesay I demonstrated the problem in my blog post. There is absolutely no refusal. It's an easily demonstrable problem.

domenic commented 11 years ago

I don't think there's any need to fear a "standards war." @pufuwozu can do his own thing, and I'm sure he'll have fun doing so, but I imagine the proportion of people using "monadises" versus Promises/A+ will be roughly equal to the proportion using Roy over ECMAScript.

ForbesLindesay commented 11 years ago

The point function in your blog post is still fantastically broken.

Raynos commented 11 years ago

The problem is really simple.

Let's take three monads. Maybe, Promise and Array. I think we can all agree they support a point operation that takes a value and returns a monad and support a flatMap operation.

Now if I wanted to write a SINGLE polymorphic function that took two monads and did an operation on them. It would need a mechanism to look at my arguments and extract both a point and flatMap operation in a standardized way.

This isn't about writing DRY and composable functions with promises. It's about doing it with monads and accepting that a promise is one of hundreds of data types and tools in our toolbox. Sure we can just drink the promise coolaid and do everything with promises but that defeats the point of composability. Composability is trivial in a dictatorship.

@pufuwozu a reasonable suggestion is to write a flatMap and point function which just has a massive switch in the body that handles all real monads and all toy monads.

puffnfresh commented 11 years ago

@domenic that's fine. I usually hang out with the people that "do the right thing" - though, I actually thought my blog post would be taken seriously and appreciated.

@ForbesLindesay yeah, it's not easy to write a minimal implementation. Will I see a real one when you've managed implement the above?

@Raynos a switch would make the assumption that the world is closed and we could never write a new monad or use a new library. I would much prefer:

Which would allow us to approximate point for most functions.

ForbesLindesay commented 11 years ago

Yep, the problem @pufuwozu is having is just that promises don't interoperate with monads. We already have a function that's broadly equivallent to how your blog post describes liftA2. We normally call it all and it takes promises, values, or arrays of promises/values in any combination and returns a promise for an array. You can see it implemented here.

To build "liftA2" on top of that:

function liftA2(arg, fn) {
  return all(arg).then(function (res) {
    return res.map(fn);
  });
}

Here is a minimal promise implementation. Most of the additional code is to handle the guarantees of asynchronous behaviour and polymorphism on return types.

As for the criticism of switches, you don't switch on library, you switch on type of object: e.g. promise, monad, array...

ForbesLindesay commented 11 years ago

As for your desire to have something like Promise.point, it is being considered for the Resolvers Spec in https://github.com/promises-aplus/resolvers-spec/issues/5

juandopazo commented 11 years ago

facepalm

You folks really need to learn to communicate this issue in terms that anyone can understand. And I mean anyone who doesn't know category theory or how Haskell works.

PS: here's the working code of the original post: http://jsbin.com/iroxeg/1/

puffnfresh commented 11 years ago

@ForbesLindesay that only works with promises. I have to write that function for every different monad. That is not DRY!

You can't switch on everything in the whole world! If I write a library that uses a point which switches on each type in each library it uses, I can't add another. I have to reimplement each of those functions. That is not DRY!

puffnfresh commented 11 years ago

@juandopazo now make it work for Arrays, optional values, promises and a to be decided structure (I have one up my sleeve).

That is what I want from the specification. I'm not sure how this message is being missed. How can I make it clearer?

michaelficarra commented 11 years ago

@pufuwozu: don't worry, there are lurkers here who certainly get the point, agree with you, and don't contribute because it couldn't possibly be made any clearer :wink:.

You're not going to get through to people in this manner if it hasn't yet happened.

Raynos commented 11 years ago

@pufuwozu write a monads module. test for flatmap and point. then whitelist all the uncompliant monads. if someone writes a new uncompliant monads, update the module, put it in the whitelist. bump the patch version and republish.

also thank you. I finally understand the point of monads. however polymorphism in JavaScript is a lost cause.

juandopazo commented 11 years ago

How can I make it clearer?

You can make it clearer by showing concrete examples of things that break, can't be done or would be much easier some other way. With code.

For instance, nowhere in your post does it say exactly what you expect point to do. Only after reading @Raynos' comment I got the idea that it's not just a wrapper for creating a promise for a certain value, but that maybe you expect it to have a different {{class}} depending on the value you want to wrap.

You want to add value to the a standard we have agreed on, I'd say the burden of explaining how it's supposed to work for "Arrays, optional values, promises and a to be decided structure" is on you.

You're not going to get through to people like this if it hasn't yet happened

I'm sincerely offended. I am trying to get something of value here and so far I haven't. Both this attitude and the troll accusations aren't helping.

michaelficarra commented 11 years ago

@juandopazo: I changed "like this" to "in this manner" because it was ambiguous and I feared people may have taken it like that. Unfortunately, you did. I did not intend to call you and others "people like this".

Twisol commented 11 years ago

@juandopazo: I just call it construct, as in a constructor, since it's more obvious what it's trying to do. It isn't standard nomenclature, but whatever. :grinning:

You want to add value to the a standard we have agreed on, I'd say the burden of explaining how it's supposed to work for "Arrays, optional values, promises and a to be decided structure" is on you.

I agree. I think there's value to be had, but Promises/A+ is at best a single kind of monad. If you want broad application of monads, there needs to be a separate discussion and exploration on what the best way to do it is. @pufuwozu's bilby library shows one possible way (and it's certainly very interesting!), but I'm not convinced that it's the only way, nor the simplest.

Raynos commented 11 years ago

@juandopazo

The above will work for any monad. List of lists? Optional optional value? If the JavaScript community settled on using flatMap as a method, we could write DRY, generalised code for monads.

He does state in the blog article that it's about polymorphism across all the monads, not just promises. Is there something here that's not obvious?

puffnfresh commented 11 years ago

I'm not sure which parts aren't clear so here we go. A 3 function unspecification of Promises/M(onadic):

Done. The second and third part are my additions. Now it's become a superset of "the monad specification". What does that let me do? This:

function map(p, f) {
    return p.then(function(a) {
        return p.constructor.point(f(a));
    });
}

function ap(p, pf) {
    return pf.then(function(f) {
        return map(p, f);
    });
}

function liftA2(pa, pb, f) {
    return ap(pb, map(pa, function(a) {
        return function(b) {
            return f(a, b);
        };
    }));
}

Now I can add a then and point to Array.prototype and Array, respectively. Or I could write a wrapper around Array, if I don't want to mutate the constructor or prototype. I can also do that to my optional value wrapper. Or any other monad that I want. I can also do it after I've defined the above function.

// Promises (called when both succeed)
liftA2(readFIle('hello.txt'), readFile('world.txt'), function(hello, world) {
    console.log(hello + ' ' + world);
});

// Optional values (only inserted into database when both exist)
liftA2(optionalUsername, optionalPassword, function(username, password) {
    insertIntoTable("user", {username: username, password: password});
});

// Arrays (function called for each element of each array)
liftA2([1, 2, 3], [4, 5, 6], function(a, b) {
    console.log(a + b);
});

So: I just want two specified functions which make it a monad. That is all. I promise :smiley:

Does the above make it any clearer?

Twisol commented 11 years ago

A promise must provide a onReject function which will handle rejections for the next then call.

This is asymmetric solely for the purposes of getting rid of a combination method. You can achieve the same power by specifying a single branch primitive that takes both callbacks, and implementing then and onReject from it.

It also seems like you're neglecting the failure path (i.e. functions chained on through onReject). We really want analogous versions of liftA2, ap, and map for both sides.

puffnfresh commented 11 years ago

@Twisol monads do somewhat gloss over the failure path. Failure path methods are provided separately. Which path is that monadic path does come down to aesthetics - which one is your "main" path? If failures are your "main" path then you will need to write a wrapper to flip it. Bad luck for that 0.000001% of cases?

rtfeldman commented 11 years ago

Hang on, I apologize if this is a stupid question, but...do people really prefer the overloaded then for aesthetic reasons?

I'm scratching my head trying to figure out why we want a method whose purpose in life is to take two other functions and then call one of them. Isn't that just an unnecessarily convoluted way to implement two methods? I'm pretty sure if I came across that in my code, my first instinct would be to refactor it into two methods.

Can someone explain this to me?

juandopazo commented 11 years ago

He does state in the blog article that it's about polymorphism across all the monads, not just promises. Is there something here that's not obvious?

Maybe it's that I just caught a cold and I'm a bit slow. Sorry if that's the case.

Does the above make it any clearer?

Yes, a lot. Thank you.

Ok, I see two issues then. The first one is how to create monads/promises of the same type of another one. That's a fair question to ask and it's something we have already discussed to an extent. It is something we're approaching with the new Promise(fn) signature. This ensures that new Promise(fn) instanceof Promise and helps with creating flavors of promises. For instance an Array promise would look like this:

function ArrayPromise() {
  Promise.apply(this, arguments);
}
ArrayPromise.prototype = Object.create(Promise.prototype);
ArrayPromise.prototype.constructor = ArrayPromise;

ArrayPromise.prototype.filter = function (fn, thisp) {
  return this.then(function (array) {
    return array.filter(fn, thisp);
  });
};

Getting a new promise from another one is just a matter of doing new promise.constructor(fn). Following that idea, I think point was discussed with the name Promise.from. I think you make a good point for why it may be important to standardize how to construct promises. Unfortunately, as I mentioned before, we already have dissimilar implementations (see WinJS and DOMFuture), so it'll be a challenge.

The constructor.point function probably has some details that can be discussed better in the issue that Raynoes opened.

The changes to then are the hard ones to justify. You ask for then to only accept functions that return promises. I agree that separate methods would be easier to follow and probably less complex to maintain. However, that's something I think we can agree that it's way too late to change. You yourself concede:

I don't really care about having bind fall back to map when it doesn't return a promise

This leaves then taking only one argument and having a separate onRejected method. And I honestly can't see the reason. then taking two arguments is really useful because of the default behavior of the second argument. When it's not provided, it's just chained to the next then call, allowing you to listen to failures in only one place. On top of that, then(callback, errback) looks like just a superset of then(callback) which you could ignore for inter-operability with other monad-like objects. And onRejected could just be a wrapper for then(null, fn).

michaelficarra commented 11 years ago

I am so happy that everyone's on the same page now :smile:.

unscriptable commented 11 years ago

This has been a fascinating and entertaining thread! The best one in promises-aplus so far!

I am fully in favor of seeing the bright minds here do the thought experiment to see if something more monad-like can replace promises -- even in Javascript.

Might I suggest that we pursue this as a thing to follow the ratification of 1.1?

-- J

On Wed, Apr 10, 2013 at 4:23 PM, Michael Ficarra notifications@github.comwrote:

I am so happy that everyone's on the same page now [image: :smile:].

— Reply to this email directly or view it on GitHubhttps://github.com/promises-aplus/promises-spec/issues/94#issuecomment-16199525 .

Raynos commented 11 years ago

@juandopazo

The first one is how to create monads/promises of the same type of another one.

It's not that we want to create an ArrayPromise that inherits from Promise. It's that we want to create a List thing that is a "monad" meaning it has a flatMap and point operator of some kind.

We can consider then as a being a superset of flatMap.

We want to write higher order functions on things that look like Functors (it has a map function), Monads (it has a flatMap and point operator) and other such constructs.

For example

function double(input) {
  return input.flatMap(function (x) {
    return input.constructor.point(x * 2)
  }) 
}

function PromiseMonad(p) {
  p.flatMap = p.then
  p.constructor.point = Promise.from
  return p
}

function ListMonad(arr) {
  return { 
    flatMap: function (f) {
      return [].concat.apply([], arr.map(f)
    },
    constructor: { point: function (x) {
      return [x]
    } } 
  }
}

We've now written double function that works on both a list of numbers and a promise for a number. We can extend it to also work on a stream of numbers and work on a function that takes a callback and returns a number and etc etc etc.

The thing to take away is not "monads are better then promises" it's that the .then semantics on an ArrayPromise probably make more sense if the onFulfilled function was called multiple times with every single value in the array and that the array returned from the onFulfilled function were concatted together to create a new ArrayPromise.

Now of course this breaks the then semantics so it makes more sense to just deal with this as an abstract monad with an abstract flatMap operation.

Twisol commented 11 years ago

On top of that, then(callback, errback) looks like just a superset of then(callback) which you could ignore for inter-operability with other monad-like objects.

Sort of. It is possible to exploit the function's arity (via f.length) to make partial function application more normal. In Haskell, if you have f :: a -> b, then f a :: b. However, in Javascript, f.bind(undefined, a) :: () -> b. If you check f.length (and make assumptions about varargs), you can make something that behaves more normally. I suspect this would be important for implementing ap.

puffnfresh commented 11 years ago

@juandopazo awesome.

As you notice, then is just an overloaded bind/flatMap. Great.

I'm happy to discuss point/from in resolvers-spec.

So handling errors will just require users to do then(null, fn) - that's awful but I'll live with it.

Sounds like it should be possible to make an implementation that is both Promises/A+ compliant and also compliant with what I'm proposing.

Thanks.

bradphelan commented 11 years ago

I'll chime in. I really have no idea about category theorey and the term monad is scary. I really didn't get it till I started looking at f# computation expressions then it just clicked.

Monads are really just a set of callbacks for an interpreter. The language that is interpreted can look imperative if you choose so.

F# has taken this to it's logical conclusion with computation expressions. http://msdn.microsoft.com/en-us/library/dd233182.aspx, an imperative embedded language where you the developer can define what each language keyword means via callbacks ( Monad methods )

Why is this good. Well functional programming is good but often you ended in nesting hell. Hence all the requests for async / await type keywords to flatten out the nesting in javascript.

computation expressions are a generalization to all kinds of flattening problems introduced by functional programming.

So in the context of this discussion making sure all your monad thingies have all the correct standard interfaces makes it possible to do things like computation expressions

So for example in F# you can do

open System.Net
open Microsoft.FSharp.Control.WebExtensions

let urlList = [ "Microsoft.com", "http://www.microsoft.com/" 
                "MSDN", "http://msdn.microsoft.com/" 
                "Bing", "http://www.bing.com"
              ]

let fetchAsync(name, url:string) =
    async { 
        try 
            let uri = new System.Uri(url)
            let webClient = new WebClient()

            // IMPORTANT BIT HERE. The code after the let!
            // executes asynchronously. Even the exception
            // handing is handled asynchronously :)

            let! html = webClient.AsyncDownloadString(uri)
            printfn "Read %d characters for %s" html.Length name
        with
            | ex -> printfn "%s" (ex.Message);
    }

let runAll() =
    urlList
    |> Seq.map fetchAsync
    |> Async.Parallel 
    |> Async.RunSynchronously
    |> ignore

runAll()

Note that the async keyword is just telling the compiler/interpreter that in this scope the monad interpreter will use the async workflow

There are callback interfaces for most of the standard f# keywords even loops and exception handling.

So in summary don't get scared off by terms like Monads and Categorey theory. Think of Monads as callbacks for an interpreter for an imperative language where you can define what each keyword in this language does. Everybody agrees on the language and everyone is free to implement any callbacks they like.

But to get it all to work you need to agree on a common set of interfaces.