New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

point function #24

Closed
Raynos opened this Issue Apr 10, 2013 · 124 comments

Comments

Projects
None yet
@Raynos

Raynos commented Apr 10, 2013

@pufuwozu brought up a good point with his article

http://brianmckenna.org/blog/category_theory_promisesaplus

The notion of having a point function that takes a value and returns a promise would allow for writing powerful higher order functions.

Basically prior art, a-la category theory shows that having a function that looks like point is a good thing, we should consider this.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

Specifically, the point function needs to take ANY value and create a fulfilled promise containing that value.

  • The function should be easily accessed, preferably on the constructor property of a promise value.
  • It can't have a special case for promises. Nested promises NEED to be possible or it will break a heap of laws, not allow deriving useful functions and break refactoring.
  • It can't take a callback to generate a value. The argument should be be the fulfilled value.

It's a very, very simple function but when combined with then, promises will form a monad. Being a monad allows us to write functions which work the same for many structures. It allows DRY code.

@juandopazo said there was possibly a proposal for a from method. Sounds good to me.

Promise.prototype.constructor = Promise;
Promise.from = function(value) {
    // Implementation of fulfilled promise that contains value
    // ...
};

puffnfresh commented Apr 10, 2013

Specifically, the point function needs to take ANY value and create a fulfilled promise containing that value.

  • The function should be easily accessed, preferably on the constructor property of a promise value.
  • It can't have a special case for promises. Nested promises NEED to be possible or it will break a heap of laws, not allow deriving useful functions and break refactoring.
  • It can't take a callback to generate a value. The argument should be be the fulfilled value.

It's a very, very simple function but when combined with then, promises will form a monad. Being a monad allows us to write functions which work the same for many structures. It allows DRY code.

@juandopazo said there was possibly a proposal for a from method. Sounds good to me.

Promise.prototype.constructor = Promise;
Promise.from = function(value) {
    // Implementation of fulfilled promise that contains value
    // ...
};
@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

I'm very keen for something broadly of this nature. I really want a way to take a promise, and create a completely new promise of the same library (i.e. with the same extension methods and helpers). Almost any time I'd use this though, I'd actually wan the behavior I've suggested elsewhere in other issues which I've called "assimilate" thus far, which essentially just looks like:

Promise.assimilate = function (valueOrThenable) {
  return new Promise(function (resolve) { resolve(valueOrThenable); });
};

This would be considerably more convenient for the use cases I have in mind, but by the sounds of things wouldn't help your use case at all. It would always be fulfilled with a value that was not a thenable/promise (or be rejected if the thenable/promise was rejected).

Could you try and explain why we need a way to create nested promises? Preferably in terms that don't require much knowledge of category theory, as I (and probably many others reading these posts) don't have vastly more knowledge of it than what you mentioned in your blog post.

Member

ForbesLindesay commented Apr 10, 2013

I'm very keen for something broadly of this nature. I really want a way to take a promise, and create a completely new promise of the same library (i.e. with the same extension methods and helpers). Almost any time I'd use this though, I'd actually wan the behavior I've suggested elsewhere in other issues which I've called "assimilate" thus far, which essentially just looks like:

Promise.assimilate = function (valueOrThenable) {
  return new Promise(function (resolve) { resolve(valueOrThenable); });
};

This would be considerably more convenient for the use cases I have in mind, but by the sounds of things wouldn't help your use case at all. It would always be fulfilled with a value that was not a thenable/promise (or be rejected if the thenable/promise was rejected).

Could you try and explain why we need a way to create nested promises? Preferably in terms that don't require much knowledge of category theory, as I (and probably many others reading these posts) don't have vastly more knowledge of it than what you mentioned in your blog post.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 11, 2013

@ForbesLindesay it's surprisingly very, very simple. The first reason is a concept called parametricity. Basically, given a function like the following:

point :: a -> Promise a

We can't tell on the inside of the implementing function what the structure of a actually is. Of course in JavaScript we can rely on duck-checking, tag-checking or something similar but that breaks the parametricity law. Breaking the law might be useful in some cases but now other laws will break!

So immediately, the next law I see breaking is the "left identity" monad law. It says these two things should always be equal:

  • Promise.from(a).then(f)
  • f(a)

What happens when a is a promise? If from "flattens" it then f will get the fulfilled content of a! Definitely not the same as just calling f(a).

Why are laws useful? Couple of reasons:

  • Lets us derive functions which we know will work for all monads, functors, applicatives, etc.
  • Lets us say which refactorings are possible (e.g. 1 + 2 can be rewritten as 2 + 1 because of the commutativity law)

Quoting "What happens to you if you break the monad laws?":

In short, "breaking the monad laws" should generally be read as "writing buggy code".

puffnfresh commented Apr 11, 2013

@ForbesLindesay it's surprisingly very, very simple. The first reason is a concept called parametricity. Basically, given a function like the following:

point :: a -> Promise a

We can't tell on the inside of the implementing function what the structure of a actually is. Of course in JavaScript we can rely on duck-checking, tag-checking or something similar but that breaks the parametricity law. Breaking the law might be useful in some cases but now other laws will break!

So immediately, the next law I see breaking is the "left identity" monad law. It says these two things should always be equal:

  • Promise.from(a).then(f)
  • f(a)

What happens when a is a promise? If from "flattens" it then f will get the fulfilled content of a! Definitely not the same as just calling f(a).

Why are laws useful? Couple of reasons:

  • Lets us derive functions which we know will work for all monads, functors, applicatives, etc.
  • Lets us say which refactorings are possible (e.g. 1 + 2 can be rewritten as 2 + 1 because of the commutativity law)

Quoting "What happens to you if you break the monad laws?":

In short, "breaking the monad laws" should generally be read as "writing buggy code".

@Raynos

This comment has been minimized.

Show comment
Hide comment
@Raynos

Raynos Apr 11, 2013

Breaking the monad laws is like the difference between a promise and a jQuery deferred. i.e. subtle bugs and having to write special cases :(

Raynos commented Apr 11, 2013

Breaking the monad laws is like the difference between a promise and a jQuery deferred. i.e. subtle bugs and having to write special cases :(

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 11, 2013

Member

@pufuwozu thanks, that was a nice explanation.

@Raynos doing something that bad would indeed be terrible.

I still think what's needed now is an example of something practical (that could be seen in a typical application) that would benefit from the monad behavior over non-monadic behavior. I think the difficulty you'll have persuading people to accept this is that most people still view promises as representations of future values (or errors) rather than as a special case of monads.

Coming from that it's difficult to see why those cases matter. I don't see anything else in JavaScript (e.g. an array) behaving like a Monad in a way that lets me write a function that's clear and simple to reads and does something useful to both a promise (with the point extension) and an array (presumably with a similar point extension).

Member

ForbesLindesay commented Apr 11, 2013

@pufuwozu thanks, that was a nice explanation.

@Raynos doing something that bad would indeed be terrible.

I still think what's needed now is an example of something practical (that could be seen in a typical application) that would benefit from the monad behavior over non-monadic behavior. I think the difficulty you'll have persuading people to accept this is that most people still view promises as representations of future values (or errors) rather than as a special case of monads.

Coming from that it's difficult to see why those cases matter. I don't see anything else in JavaScript (e.g. an array) behaving like a Monad in a way that lets me write a function that's clear and simple to reads and does something useful to both a promise (with the point extension) and an array (presumably with a similar point extension).

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 11, 2013

I think the difficulty you'll have persuading people to accept this is that most people still view promises as representations of future values (or errors) rather than as a special case of monads.

Well, they're both. Monad is an interface that a promise implements. As a result of implementing this interface, a large set of functions become available. It's a tremendous amount of free code for the cost of implementing 2 methods.

You'll find that a large set of types are monads. Combine this with the large amount of operations that apply to all Monads, and you can see that Monad is an incredibly powerful abstraction.

Lots of things are Monads and each one of them gets a stack of functions for free. What's there not to love?

It also comes at no detriment to your code. I have yet to see a single feature of the promise specs that would break as a result of implementing Monad. It's win-win.

I don't see anything else in JavaScript (e.g. an array) behaving like a Monad in a way that lets me write a function that's clear and simple to reads and does something useful to both a promise (with the point extension) and an array (presumably with a similar point extension).

Note that Monad is an abstraction, so the operations that arise from Monad are probably more abstract than you're used to. As they apply to different data types with different flatMap/point implementations, they will mean different things to different types - but the differences are encapsulated in the flatMap/point implementation - the abstract function just deals with those functions. In essence, Monad is a pattern - you demonstrate how your type matches that pattern, then the Monad operations deal with the pattern itself.

I'll give you an example: flatten.

If I have a Promise of a Promise of an x, I can flatten that to get a Promise of an x.
If I have an Array of Arrays of x, I can flatten that to get an Array of x.
If I have an Option of Option of x, I can flatten that to get an Option of x.
... repeat for all Monads... and there are a lot of them

All with one function. I don't have to write flatten for each type, I just write it once for Monad and it just works for all Monads.

Now, Monad is just one of a set of type classes / interfaces that are generally useful. Other common ones are Functor and Applicative. All Monads are Applicatives and all Applicatives are Functors. Same as Monad, if you implement these other type classes, you gain a library of functions for free.

More things are Functors than Monads, but Monad gives you more operations for free. That's why it's one we really like. Note the function "liftA2" that Brian mentioned above - the 'A' stands for Applicative. So, the type doesn't need to be a Monad to use this function - it only has to be an Applicative, which is slightly less restrictive and has some nice properties that Monad doesn't have.

And this is really just dipping your toes in the water. Open your minds to these new abstractions and a whole new world of awesome opens up right before your eyes.

techtangents commented Apr 11, 2013

I think the difficulty you'll have persuading people to accept this is that most people still view promises as representations of future values (or errors) rather than as a special case of monads.

Well, they're both. Monad is an interface that a promise implements. As a result of implementing this interface, a large set of functions become available. It's a tremendous amount of free code for the cost of implementing 2 methods.

You'll find that a large set of types are monads. Combine this with the large amount of operations that apply to all Monads, and you can see that Monad is an incredibly powerful abstraction.

Lots of things are Monads and each one of them gets a stack of functions for free. What's there not to love?

It also comes at no detriment to your code. I have yet to see a single feature of the promise specs that would break as a result of implementing Monad. It's win-win.

I don't see anything else in JavaScript (e.g. an array) behaving like a Monad in a way that lets me write a function that's clear and simple to reads and does something useful to both a promise (with the point extension) and an array (presumably with a similar point extension).

Note that Monad is an abstraction, so the operations that arise from Monad are probably more abstract than you're used to. As they apply to different data types with different flatMap/point implementations, they will mean different things to different types - but the differences are encapsulated in the flatMap/point implementation - the abstract function just deals with those functions. In essence, Monad is a pattern - you demonstrate how your type matches that pattern, then the Monad operations deal with the pattern itself.

I'll give you an example: flatten.

If I have a Promise of a Promise of an x, I can flatten that to get a Promise of an x.
If I have an Array of Arrays of x, I can flatten that to get an Array of x.
If I have an Option of Option of x, I can flatten that to get an Option of x.
... repeat for all Monads... and there are a lot of them

All with one function. I don't have to write flatten for each type, I just write it once for Monad and it just works for all Monads.

Now, Monad is just one of a set of type classes / interfaces that are generally useful. Other common ones are Functor and Applicative. All Monads are Applicatives and all Applicatives are Functors. Same as Monad, if you implement these other type classes, you gain a library of functions for free.

More things are Functors than Monads, but Monad gives you more operations for free. That's why it's one we really like. Note the function "liftA2" that Brian mentioned above - the 'A' stands for Applicative. So, the type doesn't need to be a Monad to use this function - it only has to be an Applicative, which is slightly less restrictive and has some nice properties that Monad doesn't have.

And this is really just dipping your toes in the water. Open your minds to these new abstractions and a whole new world of awesome opens up right before your eyes.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 11, 2013

BTW - here's a future library I developed ages ago: https://github.com/techtangents/jsasync

It uses "Futures" and "Asyncs" - I'm not sure if my terminology is in line with everyone else, though. Anyway, its future is a value that might be available in the future. Its Asyncs are a wrapper around functions that return a future.

In this library, I've implemented Functor, Applicative and Monad for Future, and Category and Arrow for Async. Category and Arrow are two other very useful abstractions that abstract things like functions.

If you've come across function composition, you've encountered Categories. Composition of 2 functions generalises to composition of two Categories.

techtangents commented Apr 11, 2013

BTW - here's a future library I developed ages ago: https://github.com/techtangents/jsasync

It uses "Futures" and "Asyncs" - I'm not sure if my terminology is in line with everyone else, though. Anyway, its future is a value that might be available in the future. Its Asyncs are a wrapper around functions that return a future.

In this library, I've implemented Functor, Applicative and Monad for Future, and Category and Arrow for Async. Category and Arrow are two other very useful abstractions that abstract things like functions.

If you've come across function composition, you've encountered Categories. Composition of 2 functions generalises to composition of two Categories.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 11, 2013

Concrete example:

// Constructor
function Person(name, age, postcode) {
    this.name = name;
    this.age = age;
    this.postcode = postcode;
}

var me = lift3(Person, name, age, postcode);

The lift3 will give us a Person only when all three fields "have values".

In our application code, let's pretend that name, age and postcode are promises:

name = httpGet('name');
age = httpGet('age');
postcode = httpGet('postcode');

But we don't want to make HTTP requests in our unit tests! Let's make them "optional values" to see if the logic still works:

name = someValue('Brian');
age = someValue(23);

postcode = noValue;
run();
assert(me == noValue);

postcode = someValue(4017);
run();
assert(me.get().postcode = 4017);

Both promises and optional values are monads with similar semantics. The me value only relies on the monad class so we can make it asynchronous or synchronous just by changing the monad (i.e. the wrapper around name, age and postcode)!

puffnfresh commented Apr 11, 2013

Concrete example:

// Constructor
function Person(name, age, postcode) {
    this.name = name;
    this.age = age;
    this.postcode = postcode;
}

var me = lift3(Person, name, age, postcode);

The lift3 will give us a Person only when all three fields "have values".

In our application code, let's pretend that name, age and postcode are promises:

name = httpGet('name');
age = httpGet('age');
postcode = httpGet('postcode');

But we don't want to make HTTP requests in our unit tests! Let's make them "optional values" to see if the logic still works:

name = someValue('Brian');
age = someValue(23);

postcode = noValue;
run();
assert(me == noValue);

postcode = someValue(4017);
run();
assert(me.get().postcode = 4017);

Both promises and optional values are monads with similar semantics. The me value only relies on the monad class so we can make it asynchronous or synchronous just by changing the monad (i.e. the wrapper around name, age and postcode)!

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 11, 2013

Member

OK, thanks, this has been really useful and informative. What I'd like to propose we do to move things forwards is:

  1. Make sure we don't prohibit having a method to create a promise for a promise in any of the specs (I still don't fully understand the implications of doing so, but I'll take your word for the fact that it's important).
  2. Create a separate spec for how "Monadic Promises" should work. This would include the point, flatMap and onRejected operations, with exactly the semantics desirable for them to be monads.

This spec needs to include a duck typing test for "Monadic Support" (I'm not sure if "monadic" is the right word for "like a monad"). It will be optional, in the sense that not all promise libraries will support it, but if it proves useful, many will support it. You can then write libraries that require their promises implement the "Monadic Promises" spec. They can just either wrap promises that don't implement "Monadic Support" or throw a TypeError.

This will keep the core APIs for consuming and creating promises simple for people who have approached the problem from a software development background (e.g. callbacks) rather than a category theory background, because they can continue to use the simple to understand .then method and the constructor proposed here. But it will allow libraries to support more advanced functionality.

Member

ForbesLindesay commented Apr 11, 2013

OK, thanks, this has been really useful and informative. What I'd like to propose we do to move things forwards is:

  1. Make sure we don't prohibit having a method to create a promise for a promise in any of the specs (I still don't fully understand the implications of doing so, but I'll take your word for the fact that it's important).
  2. Create a separate spec for how "Monadic Promises" should work. This would include the point, flatMap and onRejected operations, with exactly the semantics desirable for them to be monads.

This spec needs to include a duck typing test for "Monadic Support" (I'm not sure if "monadic" is the right word for "like a monad"). It will be optional, in the sense that not all promise libraries will support it, but if it proves useful, many will support it. You can then write libraries that require their promises implement the "Monadic Promises" spec. They can just either wrap promises that don't implement "Monadic Support" or throw a TypeError.

This will keep the core APIs for consuming and creating promises simple for people who have approached the problem from a software development background (e.g. callbacks) rather than a category theory background, because they can continue to use the simple to understand .then method and the constructor proposed here. But it will allow libraries to support more advanced functionality.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 11, 2013

Member

If @paulmillr, @Raynos, @pufuwozu, @techtangents and the obvious key individuals in the promises-aplus organisation are happy with that as a way to proceed, someone can create another repository for this monad spec.

Additionally, I wonder whether the Category Theory/Monads world has anything interesting to say about cancellation or progress, as both specs are still very much a work in progress and they're thorny issues which are not yet well understood via being implemented & used.

Member

ForbesLindesay commented Apr 11, 2013

If @paulmillr, @Raynos, @pufuwozu, @techtangents and the obvious key individuals in the promises-aplus organisation are happy with that as a way to proceed, someone can create another repository for this monad spec.

Additionally, I wonder whether the Category Theory/Monads world has anything interesting to say about cancellation or progress, as both specs are still very much a work in progress and they're thorny issues which are not yet well understood via being implemented & used.

@techtangents

This comment has been minimized.

Show comment
Hide comment

techtangents commented Apr 11, 2013

@paulmillr

This comment has been minimized.

Show comment
Hide comment
@paulmillr

paulmillr Apr 11, 2013

+1 for from method. ECMAScript 6 specifies Array.from and maybe Set.from.

paulmillr commented Apr 11, 2013

+1 for from method. ECMAScript 6 specifies Array.from and maybe Set.from.

@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Apr 11, 2013

-1 for using from as in ES6 Array.from. It is more like our assimilate procedure, and I would expect Promise.from to take any thenable and transform it into a valid promise.

Instead, I'd opt for Promise.of as the point/return function whose point is to wrap a value into a Promise for it. This resembles Array.of, and I think it is also more descriptive than "from".

bergus commented Apr 11, 2013

-1 for using from as in ES6 Array.from. It is more like our assimilate procedure, and I would expect Promise.from to take any thenable and transform it into a valid promise.

Instead, I'd opt for Promise.of as the point/return function whose point is to wrap a value into a Promise for it. This resembles Array.of, and I think it is also more descriptive than "from".

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 11, 2013

@paulmillr wow, thanks for that. I took a look at the spec - from is not what we want, we want of!

http://people.mozilla.org/~jorendorff/es6-draft.html#sec-15.4.3.4

> Array.of(1)
[ 1 ]

I propose we name this method Promise.of - it will work very similar to Array.of.

puffnfresh commented Apr 11, 2013

@paulmillr wow, thanks for that. I took a look at the spec - from is not what we want, we want of!

http://people.mozilla.org/~jorendorff/es6-draft.html#sec-15.4.3.4

> Array.of(1)
[ 1 ]

I propose we name this method Promise.of - it will work very similar to Array.of.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 11, 2013

+1
As an aside: Google Guava also uses 'of' in constructing their data structures. e.g. ImmutableList.of(3).

techtangents commented Apr 11, 2013

+1
As an aside: Google Guava also uses 'of' in constructing their data structures. e.g. ImmutableList.of(3).

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 11, 2013

So immediately, the next law I see breaking is the "left identity" monad law. It says these two things should always be equal:

  • Promise.from(a).then(f)
  • f(a)

We've run into this issue when discussing the behavior of resolver.fulfill. The problem is with the overloaded then. Take the identity function:

function identity(x) {
  return x;
}

One would expect that promise.then(identity).then(identity) would return a promise for the same value as the first promise. However, if point lets you create a promise of a promise, then

Promise.point(somePromise).then(identity).then(function (value) {
  assert(value === somePromise); // false
});

juandopazo commented Apr 11, 2013

So immediately, the next law I see breaking is the "left identity" monad law. It says these two things should always be equal:

  • Promise.from(a).then(f)
  • f(a)

We've run into this issue when discussing the behavior of resolver.fulfill. The problem is with the overloaded then. Take the identity function:

function identity(x) {
  return x;
}

One would expect that promise.then(identity).then(identity) would return a promise for the same value as the first promise. However, if point lets you create a promise of a promise, then

Promise.point(somePromise).then(identity).then(function (value) {
  assert(value === somePromise); // false
});
@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 11, 2013

Member

One of many reasons that a "promise for a promise" is undesirable. When @domenic said you need to start by writing code, I think he was correct. Begin by writing something that does Promises/A+ but with your extensions. https://github.com/nathan7/pledge/blob/master/pledge.js is about the most minimal implementation I've seen, so would be a good starting point. build your promise monad, make it still pass the promises/A+ test wuite (shouldn't be too difficult). Build a cool (and open source) real-world application that uses it, refine it as needed, then lets talk about incorporating the good bits into currently active promise libraries.

I'm voting to close this issue now as I don't think point or of is going to make it into v1 of the resolvers-spec.

Member

ForbesLindesay commented Apr 11, 2013

One of many reasons that a "promise for a promise" is undesirable. When @domenic said you need to start by writing code, I think he was correct. Begin by writing something that does Promises/A+ but with your extensions. https://github.com/nathan7/pledge/blob/master/pledge.js is about the most minimal implementation I've seen, so would be a good starting point. build your promise monad, make it still pass the promises/A+ test wuite (shouldn't be too difficult). Build a cool (and open source) real-world application that uses it, refine it as needed, then lets talk about incorporating the good bits into currently active promise libraries.

I'm voting to close this issue now as I don't think point or of is going to make it into v1 of the resolvers-spec.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 11, 2013

I think this thread just took a turn for the worse.

Can you please explain what's going on here?

One would expect that promise.then(identity).then(identity) would return a promise for the same value as the first promise. However, if point lets you create a promise of a promise, then

Promise.point(somePromise).then(identity).then(function (value) {
assert(value === somePromise); // false
});

techtangents commented Apr 11, 2013

I think this thread just took a turn for the worse.

Can you please explain what's going on here?

One would expect that promise.then(identity).then(identity) would return a promise for the same value as the first promise. However, if point lets you create a promise of a promise, then

Promise.point(somePromise).then(identity).then(function (value) {
assert(value === somePromise); // false
});

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 11, 2013

We've run into this issue when discussing the behavior of resolver.fulfill. The problem is with the overloaded 'then'

Perhaps this is the problem. Having functions that do different things depending on the type is a bad idea. It impacts your ability to reason about what the function does, and breaks the parametricity law.

Looking at http://brianmckenna.org/blog/category_theory_promisesaplus, it looks like you're trying to use 'then' to be map and flatmap - this is a bad idea. They're completely different concepts. For a promise, flatMap chains the result of an asynchronous computation into another asynchronouse computation, returning a Promise of the combined result. Map, on the other hand, just runs a (synchronous) function over the output. Very different use cases.

If you're writing a spec to be used by multiple implementations, you can't afford to make this sort of mistake. It just has "bug" written all over it.

Let me be clear: parametricity lets you reason about a type-parameterised function independently of the type being passed in. i.e. you can reason about what effect map or flatMap independently of what type of value is being passed in. This is important everywhere, but has particular importance for a dynamically-typed language.

techtangents commented Apr 11, 2013

We've run into this issue when discussing the behavior of resolver.fulfill. The problem is with the overloaded 'then'

Perhaps this is the problem. Having functions that do different things depending on the type is a bad idea. It impacts your ability to reason about what the function does, and breaks the parametricity law.

Looking at http://brianmckenna.org/blog/category_theory_promisesaplus, it looks like you're trying to use 'then' to be map and flatmap - this is a bad idea. They're completely different concepts. For a promise, flatMap chains the result of an asynchronous computation into another asynchronouse computation, returning a Promise of the combined result. Map, on the other hand, just runs a (synchronous) function over the output. Very different use cases.

If you're writing a spec to be used by multiple implementations, you can't afford to make this sort of mistake. It just has "bug" written all over it.

Let me be clear: parametricity lets you reason about a type-parameterised function independently of the type being passed in. i.e. you can reason about what effect map or flatMap independently of what type of value is being passed in. This is important everywhere, but has particular importance for a dynamically-typed language.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 11, 2013

Let me use an array analogy.

map for arrays maps a function over each element in an array.
e.g. map([1, 2, 3], function(x) { return x + 1; }); returns [2, 3, 4];

flatMap for arrays maps a function that returns another array over the array, then collapses the result.
e.g. flatMap([1, 2, 3], function(x) { return [x, String(x)]; }; returns [1, "1", 2, "2", 3, "3"];

Now, what if I were to write this:
map([1, 2, 3], function(x) { return [x, String(x)]; };
I would expect this to return:
[[1, "1"], [2, "2"], [3, "3"]]

Note that map and flatMap of the same function over the same array return different values.

However, if I only had a "then" function that chose map and flatMap, one of those behaviors would be impossible to write.

techtangents commented Apr 11, 2013

Let me use an array analogy.

map for arrays maps a function over each element in an array.
e.g. map([1, 2, 3], function(x) { return x + 1; }); returns [2, 3, 4];

flatMap for arrays maps a function that returns another array over the array, then collapses the result.
e.g. flatMap([1, 2, 3], function(x) { return [x, String(x)]; }; returns [1, "1", 2, "2", 3, "3"];

Now, what if I were to write this:
map([1, 2, 3], function(x) { return [x, String(x)]; };
I would expect this to return:
[[1, "1"], [2, "2"], [3, "3"]]

Note that map and flatMap of the same function over the same array return different values.

However, if I only had a "then" function that chose map and flatMap, one of those behaviors would be impossible to write.

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 12, 2013

Yes, that's the issue with promises for promises. And IMHO it's unfortunately too late to fix it. There is a lot of code out there that depends on this being "broken".

juandopazo commented Apr 12, 2013

Yes, that's the issue with promises for promises. And IMHO it's unfortunately too late to fix it. There is a lot of code out there that depends on this being "broken".

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 12, 2013

Well, isn't it just the "then" function that has this mixed map/flatMap behavior? Sorry if this has already been asked, but could you just add map and flatMap? "then" behaves as-is, and map and flatMap behave like the Functor and Monad interfaces require.

Would this be a workable solution? Old code keeps working and you get to implement Monad.

techtangents commented Apr 12, 2013

Well, isn't it just the "then" function that has this mixed map/flatMap behavior? Sorry if this has already been asked, but could you just add map and flatMap? "then" behaves as-is, and map and flatMap behave like the Functor and Monad interfaces require.

Would this be a workable solution? Old code keeps working and you get to implement Monad.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 12, 2013

I think I've been pretty good with giving examples of where things will break. Can someone do the same with of?

I don't know the reason why it can't be properly implemented. Thanks.

puffnfresh commented Apr 12, 2013

I think I've been pretty good with giving examples of where things will break. Can someone do the same with of?

I don't know the reason why it can't be properly implemented. Thanks.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 12, 2013

Member

It can't be implemented if you require promises for promises using the existing then behavior. That isn't going to change because promises for promises don't actually make sense as a concept. It makes sense for some monads, but not for promises. As such, it's not going to change.

I'd suggest you create an orthogonal spec that specifies the behavior of a flatMap method, but don't overload then if you want anyone to actually use it.

Member

ForbesLindesay commented Apr 12, 2013

It can't be implemented if you require promises for promises using the existing then behavior. That isn't going to change because promises for promises don't actually make sense as a concept. It makes sense for some monads, but not for promises. As such, it's not going to change.

I'd suggest you create an orthogonal spec that specifies the behavior of a flatMap method, but don't overload then if you want anyone to actually use it.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 12, 2013

Promises of promises do make sense as a concept. I've used them in other implementations.

To be a monad, it has to allow nesting (like I showed above), otherwise it is not a monad.

What I think you're saying is that Promises/A+ can not define a monad because it can not define an of function. I am trying to ask "why?"

I have pointed at laws and given example code whenever I say something won't work. Can someone please do something similar?

If it is a problem with Promises/A+ then that is sadly a missed opportunity :(

People are already using Fantasy Land but thanks for the feedback.

puffnfresh commented Apr 12, 2013

Promises of promises do make sense as a concept. I've used them in other implementations.

To be a monad, it has to allow nesting (like I showed above), otherwise it is not a monad.

What I think you're saying is that Promises/A+ can not define a monad because it can not define an of function. I am trying to ask "why?"

I have pointed at laws and given example code whenever I say something won't work. Can someone please do something similar?

If it is a problem with Promises/A+ then that is sadly a missed opportunity :(

People are already using Fantasy Land but thanks for the feedback.

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 12, 2013

I have pointed at laws and given example code whenever I say something won't work. Can someone please do something similar?

I did. See above: #24 (comment)

juandopazo commented Apr 12, 2013

I have pointed at laws and given example code whenever I say something won't work. Can someone please do something similar?

I did. See above: #24 (comment)

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 12, 2013

@juandopazo ah thanks, forgot to reply to that.

Functor laws as specified in Fantasy Land:

  • u.map(function(a) { return a; })) equals u
  • u.map(function(x) { return f(g(x)); }) equals u.map(g).map(f)

It's really great to see that you want to preserve the first Functor law! In a language with types, it's proven to always be true so we don't even have to worry about it (due to parametricity). How cool is that!?

Anyway, if we were able to of a promise to a promise and get a nested promise then it would trigger then's overloaded functionality and it wouldn't behave like map 😞

But then we satisfy the monad laws and can derive lots of useful methods. For example, a map that satisfies the functor laws!

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

So you see that you're saying that having no of means that we can have a map that works for any function that doesn't return a promise. I'm saying that having an of means we can implement a map that works for any function that returns anything!

More equivalence laws the better. Laws allow us to derive useful functionality and easily reason about what code will do what. You've said that we want the functor laws. Great. Why don't we want to add one function to satisfy the monad laws as well?

puffnfresh commented Apr 12, 2013

@juandopazo ah thanks, forgot to reply to that.

Functor laws as specified in Fantasy Land:

  • u.map(function(a) { return a; })) equals u
  • u.map(function(x) { return f(g(x)); }) equals u.map(g).map(f)

It's really great to see that you want to preserve the first Functor law! In a language with types, it's proven to always be true so we don't even have to worry about it (due to parametricity). How cool is that!?

Anyway, if we were able to of a promise to a promise and get a nested promise then it would trigger then's overloaded functionality and it wouldn't behave like map 😞

But then we satisfy the monad laws and can derive lots of useful methods. For example, a map that satisfies the functor laws!

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

So you see that you're saying that having no of means that we can have a map that works for any function that doesn't return a promise. I'm saying that having an of means we can implement a map that works for any function that returns anything!

More equivalence laws the better. Laws allow us to derive useful functionality and easily reason about what code will do what. You've said that we want the functor laws. Great. Why don't we want to add one function to satisfy the monad laws as well?

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 12, 2013

That sounds nice, but it's sadly breaking expectations one way or another. With the overloaded then we either break the left identity law or we break the first functor law. I'd say we're in a pickle.

juandopazo commented Apr 12, 2013

That sounds nice, but it's sadly breaking expectations one way or another. With the overloaded then we either break the left identity law or we break the first functor law. I'd say we're in a pickle.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 12, 2013

@juandopazo so if of is doesn't work for promise values, then there will be broken laws. If of does work for promise values, it's possible to work around and create methods that don't have broken laws.

I really, really want a non-broken of so I can work around it - that's a much better pickle.

puffnfresh commented Apr 12, 2013

@juandopazo so if of is doesn't work for promise values, then there will be broken laws. If of does work for promise values, it's possible to work around and create methods that don't have broken laws.

I really, really want a non-broken of so I can work around it - that's a much better pickle.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 16, 2013

Well, glad you were convinced one minute before my example 😄

puffnfresh commented Apr 16, 2013

Well, glad you were convinced one minute before my example 😄

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 16, 2013

@ForbesLindesay the Fantasy Land specification allows of to be on the value or the values constructor. Either one. Code abstracting over it must fall back to either.

puffnfresh commented Apr 16, 2013

@ForbesLindesay the Fantasy Land specification allows of to be on the value or the values constructor. Either one. Code abstracting over it must fall back to either.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

OK, to be clear, I'm happy that this needs further consideration, and more eyes on it from the promises side of the fence, rather than just me + a few people in neither camp + everyone from fantasy land (I still think that's a terrible name for a spec you want people to actually use).

Just a point of clarification: Why can't you fix the above by just making Identity also do the same recursive flattening?

Member

ForbesLindesay commented Apr 16, 2013

OK, to be clear, I'm happy that this needs further consideration, and more eyes on it from the promises side of the fence, rather than just me + a few people in neither camp + everyone from fantasy land (I still think that's a terrible name for a spec you want people to actually use).

Just a point of clarification: Why can't you fix the above by just making Identity also do the same recursive flattening?

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

P.S. Link to my summary gist: https://gist.github.com/ForbesLindesay/5392612

The corresponding arguments for will need to be a bit longer and include examples as those arguments are less well understood by the promises community.

Member

ForbesLindesay commented Apr 16, 2013

P.S. Link to my summary gist: https://gist.github.com/ForbesLindesay/5392612

The corresponding arguments for will need to be a bit longer and include examples as those arguments are less well understood by the promises community.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 16, 2013

Map can be implemented on top of then and of if we get it to flatten a single layer rather than multiple layers.

Say what?

map shouldn't do any flattening. Map should take a Promise and a function and return a Promise that runs the function over the result of the original Promise. Just like how map over an array works.

"map" comes from the Functor type. The Functor laws state that map should return a value of the same structure. So, take a Promise of x, run a function x -> y over it and get a Promise of y.

One way of thinking of "flatMap" is that it maps then flattens.

As I understand it, this bizarre "then" function would be written in terms of map and flatMap.

techtangents commented Apr 16, 2013

Map can be implemented on top of then and of if we get it to flatten a single layer rather than multiple layers.

Say what?

map shouldn't do any flattening. Map should take a Promise and a function and return a Promise that runs the function over the result of the original Promise. Just like how map over an array works.

"map" comes from the Functor type. The Functor laws state that map should return a value of the same structure. So, take a Promise of x, run a function x -> y over it and get a Promise of y.

One way of thinking of "flatMap" is that it maps then flattens.

As I understand it, this bizarre "then" function would be written in terms of map and flatMap.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 16, 2013

@ForbesLindesay as for limiting Identity of Identity. How far does it go? There's actual use cases for Optional Optional values. Or Either Either values. It also ruins parametricity, as @techtangents pointed out.

puffnfresh commented Apr 16, 2013

@ForbesLindesay as for limiting Identity of Identity. How far does it go? There's actual use cases for Optional Optional values. Or Either Either values. It also ruins parametricity, as @techtangents pointed out.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 16, 2013

And it breaks the Monad laws.

techtangents commented Apr 16, 2013

And it breaks the Monad laws.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 16, 2013

@ForbesLindesay just s/Identity/Optional/g in my comment. You need to represent Optional Optional values.

puffnfresh commented Apr 16, 2013

@ForbesLindesay just s/Identity/Optional/g in my comment. You need to represent Optional Optional values.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member
function map(entity, fn) {
  if (typeof entity.map === 'function') {
    return entity.map(fn);
  } else if (entity.chain === 'function') {
    return entity.chain(function (val) { return entity.constructor.of(fn(val)); });
  } else if (entity.then === 'function') {// && if then only flattens one layer, not multiple
    return entity.then(function (val) { return entity.constructor.of(fn(val)); });
  }
}
Member

ForbesLindesay commented Apr 16, 2013

function map(entity, fn) {
  if (typeof entity.map === 'function') {
    return entity.map(fn);
  } else if (entity.chain === 'function') {
    return entity.chain(function (val) { return entity.constructor.of(fn(val)); });
  } else if (entity.then === 'function') {// && if then only flattens one layer, not multiple
    return entity.then(function (val) { return entity.constructor.of(fn(val)); });
  }
}
@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

In answer to @techtangents' "say what?"

Member

ForbesLindesay commented Apr 16, 2013

In answer to @techtangents' "say what?"

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

I still find the idea of an optional optional a bit weird, it would be hugely helpful if someone had an example, but I'll accept it for now.

Member

ForbesLindesay commented Apr 16, 2013

I still find the idea of an optional optional a bit weird, it would be hugely helpful if someone had an example, but I'll accept it for now.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 16, 2013

That's really weird.

Return a promise which evaluates the original promise, but maps a function over it.

function map(promise, fn) {
  return promise(function(x) {
    callback(fn(x))
  });
}

techtangents commented Apr 16, 2013

That's really weird.

Return a promise which evaluates the original promise, but maps a function over it.

function map(promise, fn) {
  return promise(function(x) {
    callback(fn(x))
  });
}
@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 16, 2013

I still find the idea of an optional optional a bit weird, it would be hugely helpful if someone had an example, but I'll accept it for now.

Well, you can think of an Optional as a List of either zero or 1 elements. Now think of a List of Lists.

techtangents commented Apr 16, 2013

I still find the idea of an optional optional a bit weird, it would be hugely helpful if someone had an example, but I'll accept it for now.

Well, you can think of an Optional as a List of either zero or 1 elements. Now think of a List of Lists.

@charleso

This comment has been minimized.

Show comment
Hide comment
@charleso

charleso Apr 16, 2013

I still find the idea of an optional optional a bit weird, it would be hugely helpful if someone had an example

James Iry has an example of that:

You're writing a cache system (in the form of an associative map) in front of a persistent storage with nullable values. Question: how do you differentiate between "I haven't cached this value" from "I've cached the value and it was null in the database"? The answer is that you can't use "null" to mean both things, it's ambiguous.

charleso commented Apr 16, 2013

I still find the idea of an optional optional a bit weird, it would be hugely helpful if someone had an example

James Iry has an example of that:

You're writing a cache system (in the form of an associative map) in front of a persistent storage with nullable values. Question: how do you differentiate between "I haven't cached this value" from "I've cached the value and it was null in the database"? The answer is that you can't use "null" to mean both things, it's ambiguous.

@Raynos

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

Excellent example, thanks @charleso .

@techtangents I don't get what you're not happy with, it's even mentioned as something you can do in the Fantasy Land Spec.

Member

ForbesLindesay commented Apr 16, 2013

Excellent example, thanks @charleso .

@techtangents I don't get what you're not happy with, it's even mentioned as something you can do in the Fantasy Land Spec.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

@Raynos just created https://gist.github.com/ForbesLindesay/5392701 I want to gather two separate gists and make each as strong as I can. I'll start incorporating other examples/counter arguments into each.

Member

ForbesLindesay commented Apr 16, 2013

@Raynos just created https://gist.github.com/ForbesLindesay/5392701 I want to gather two separate gists and make each as strong as I can. I'll start incorporating other examples/counter arguments into each.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 16, 2013

@techtangents I don't get what you're not happy with, it's even mentioned as something you can do in the Fantasy Land Spec.

I must have missed something. Is this a way to provide a map implementation for things that have either map, chain or then, as an interoperability thing?

techtangents commented Apr 16, 2013

@techtangents I don't get what you're not happy with, it's even mentioned as something you can do in the Fantasy Land Spec.

I must have missed something. Is this a way to provide a map implementation for things that have either map, chain or then, as an interoperability thing?

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

Yep, it aims to implement map for anything which has either map, (chain and of) or (then and of but where then behaves like chain and only flattens one level).

P.S. if you put a new line after the quoted bit, it won't render the whole thing as a quote, it would also be helpful if you could go back and fix the formatting of a few past posts.

Member

ForbesLindesay commented Apr 16, 2013

Yep, it aims to implement map for anything which has either map, (chain and of) or (then and of but where then behaves like chain and only flattens one level).

P.S. if you put a new line after the quoted bit, it won't render the whole thing as a quote, it would also be helpful if you could go back and fix the formatting of a few past posts.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

@Raynos I've attempted to incorporate your example into the gist so I've deleted your comment as I want to keep them both free of commentary. I've included your arguments (rephrased slightly) in https://gist.github.com/ForbesLindesay/5392701 There may be more to add, if so, please make suggestions in comments on that gist not the gist giving arguments against promises for promises.

Member

ForbesLindesay commented Apr 16, 2013

@Raynos I've attempted to incorporate your example into the gist so I've deleted your comment as I want to keep them both free of commentary. I've included your arguments (rephrased slightly) in https://gist.github.com/ForbesLindesay/5392701 There may be more to add, if so, please make suggestions in comments on that gist not the gist giving arguments against promises for promises.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

How do people feel about my previously proposed suggestion for ultimately closing this issue. I feel the discussion has gone on too long with too much back and forth repetition in this issue to expect newcomers to catch up. This is why I want to start a fresh with just a summary of the arguments for and against that were discussed in this issue.

Member

ForbesLindesay commented Apr 16, 2013

How do people feel about my previously proposed suggestion for ultimately closing this issue. I feel the discussion has gone on too long with too much back and forth repetition in this issue to expect newcomers to catch up. This is why I want to start a fresh with just a summary of the arguments for and against that were discussed in this issue.

@Twisol

This comment has been minimized.

Show comment
Hide comment
@Twisol

Twisol Apr 16, 2013

@ForbesLindesay: Sounds like a good proposal to me. I'm glad to hear about your moment of epiphany!

Twisol commented Apr 16, 2013

@ForbesLindesay: Sounds like a good proposal to me. I'm glad to hear about your moment of epiphany!

@Raynos

This comment has been minimized.

Show comment
Hide comment
@Raynos

Raynos Apr 16, 2013

@ForbesLindesay what we really need is two new issues in the correct place. One with an amendum to .then() to not recursively flatten. The other is an amendum to whatever to add .of() (this probably requires speccing out the semantics of of() in more detail).

Raynos commented Apr 16, 2013

@ForbesLindesay what we really need is two new issues in the correct place. One with an amendum to .then() to not recursively flatten. The other is an amendum to whatever to add .of() (this probably requires speccing out the semantics of of() in more detail).

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

I'm happy to see that issue for of. I just think that, since we're very close to releasing a new promise spec that would forever make this a backwards incompatible change to the spec, we should focus on that for the time being.

If you ask for one, small change, and justify it, it's much more likely to get proper consideration than if you open lots of simultaneous issues asking to change everything. Lets first argue for the value behind only unwrapping a single layer, because if we have that, we can move forwards without breaking backwards compatibility. To this end, I have opened promises-aplus/promises-spec#101 which I think should serve as the only place for discussion on this issue for the time being.

If we can agree there that we want promises for promises to be a possible thing, you're very unlikely to see any arguments regarding promise.of being specc'd in a very simple, sane, way.

Member

ForbesLindesay commented Apr 16, 2013

I'm happy to see that issue for of. I just think that, since we're very close to releasing a new promise spec that would forever make this a backwards incompatible change to the spec, we should focus on that for the time being.

If you ask for one, small change, and justify it, it's much more likely to get proper consideration than if you open lots of simultaneous issues asking to change everything. Lets first argue for the value behind only unwrapping a single layer, because if we have that, we can move forwards without breaking backwards compatibility. To this end, I have opened promises-aplus/promises-spec#101 which I think should serve as the only place for discussion on this issue for the time being.

If we can agree there that we want promises for promises to be a possible thing, you're very unlikely to see any arguments regarding promise.of being specc'd in a very simple, sane, way.

@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

This comment has been minimized.

Show comment
Hide comment
@techtangents

techtangents Apr 16, 2013

Also, @ForbesLindesay, you said you didn't understand liftA2. I've tried to explain this in http://www.techtangents.com/explanation-of-lifta2-in-javascript/

There are lots of other tutorials on Functors, Applicatives and Monads out there. Most of them are in Haskell or Scala, though, not JavaScript. I would highly recommend you learn Haskell as it makes it easier to understand these concepts, so you can then apply them to your coding in other languages.

techtangents commented Apr 16, 2013

Also, @ForbesLindesay, you said you didn't understand liftA2. I've tried to explain this in http://www.techtangents.com/explanation-of-lifta2-in-javascript/

There are lots of other tutorials on Functors, Applicatives and Monads out there. Most of them are in Haskell or Scala, though, not JavaScript. I would highly recommend you learn Haskell as it makes it easier to understand these concepts, so you can then apply them to your coding in other languages.

@briancavalier

This comment has been minimized.

Show comment
Hide comment
@briancavalier

briancavalier Apr 16, 2013

Member

There's a ton of good information in this thread. Thanks, everyone.

I agree with @ForbesLindesay that it has reached the point where it's best to close and direct energy toward promises-aplus/promises-spec#101, specifically noting the process that has been set forth there.

Member

briancavalier commented Apr 16, 2013

There's a ton of good information in this thread. Thanks, everyone.

I agree with @ForbesLindesay that it has reached the point where it's best to close and direct energy toward promises-aplus/promises-spec#101, specifically noting the process that has been set forth there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment