Incorporate monads and category theory #94

Closed
paulmillr opened this Issue Apr 10, 2013 · 210 comments

Projects

None yet
@paulmillr

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 promises-aplus/constructor-spec#24 for more discussion

@bergus
bergus commented Apr 10, 2013

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
Member
domenic commented Apr 10, 2013

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.

@domenic domenic closed this Apr 10, 2013
@briancavalier
Member

@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

@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
Contributor

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

@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
Member

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
Member

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
Member

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
Contributor

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
Member

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

@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
Member

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
Contributor

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

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
Twisol commented Apr 10, 2013

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. 😀

@puffnfresh

@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
Contributor

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

@ForbesLindesay
Member

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

+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

@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
Twisol commented Apr 10, 2013

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
Member

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

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

@domenic
Member
domenic commented Apr 10, 2013

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
Member

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

@Raynos
Raynos commented Apr 10, 2013

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

@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:

  • Promise.point = function() { /* ... */ };
  • Promise.prototype.constructor = Promise;

Which would allow us to approximate point for most functions.

@ForbesLindesay
Member

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
Member

As for your desire to have something like Promise.point, it is being considered for the Resolvers Spec in promises-aplus/constructor-spec#5

@juandopazo
Contributor

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

@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

@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

@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 😉.

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

@Raynos
Raynos commented Apr 10, 2013

@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
Contributor

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

@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
Twisol commented Apr 10, 2013

@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. 😀

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
Raynos commented Apr 10, 2013

@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

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

  • A promise must provide a then function. It takes a function which itself will return a promise. The then function returns a promise and schedules the given function. When the given function is resolved, it should sync state with the promise returned by then.
  • A promise must provide a constructor which has a point method. The point method will create a new promise, containing the given value.
  • A promise must provide a onReject function which will handle rejections for the next then call.

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 😃

Does the above make it any clearer?

@Twisol
Twisol commented Apr 10, 2013

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

@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

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
Contributor

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

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

@unscriptable
Member

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: 😄].


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

@Raynos
Raynos commented Apr 10, 2013

@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
Twisol commented Apr 10, 2013

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

@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

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.

@briancavalier
Member

Thanks to the Promises/A+ folks for asking really good questions, and thanks to @pufuwozu (and others) for answering with lots of good information and examples. I'm basically in favor of anything that makes promises "better" and more useful, without also making them less approachable/learnable.

My main concerns are:

Approachability/Learnability - it's been the experience of many Promises/A+ contributors that Promises, in general, already are daunting to developers. It's at least been my experience that Monads, in general, can also be daunting to folks. I think that we need to be careful not create something that is even harder for developers to grok/use. As a super simple example, I fear that the name point is not intuitive for what it does, and would confuse many developers. It's great that @pufuwozu has said over in #24 that he's cool with one of our previously proposed names from. If monadic promises can be theoretically strong, but also no more difficult to learn, I think that kind of synergy of theory and practicality can be a big win.

Interoperability - there's a substantial and growing amount of deployed code based on non-promise thenables, Promises/A, and Promises/A+. I think we have to consider that, and make sure we don't break the world. For example, changes to the signature and/or behavior of then could be a big barrier adoption, or worse, could cause developers some serious headaches. Again, it seems like it's possible to move forward in a way that maintains compatibility/interoperability, but it's something we need to keep in mind as we explore this approach.

Promises/A+ 1.1 timing - This version is effectively "done", and I think we should release it. We can continue to explore monadic promises for a subsequent version.

DOM Futures - It's not clear to me how this could/would/should influence the current DOM Futures work, but maintaining compatibility seems very important. I'm hoping someone more involved with that effort (@domenic, @erights, @wycats?) is able to comment.

It would also be good to hear from other lib authors, especially @kriskowal, @erights, @wycats, @lsmith, and @novemberborn.

@robotlolita

@briancavalier As far as approachability/learnability goes, I believe Monads provide a much simpler interface for Promises/A+ (even ignoring the "being more composable" argument, which is a rather strong one). I think we should value simplicity over a seemingly easy but complex API (plus we get more compositionality yay!). The current specification for then is really complex, it merges lots of concepts and deals with a fair bit of automatic conversion for the user. While this does allow a particular user "type less" in the general case, it's not something that really scales on the long run (see the Abstract Equality Operator).

People don't need to be concerned about what Monads are or aren't to use Promises. I think that's an important part of this discussion I don't recall people mentioning before. Users who don't get Category Theory, Monoids, Monads, Functors, Applicatives and all that stuff (which would include myself for now :3) can just look at this whole thing as more combinators. Functional idioms are beginning to spread throughout the JS community, even though people don't grok all the internal details of those idioms: function composition, generic and specialised folds, etc. So I think it's worth a shot working together to construct an API that is simple (by the means of separating and composing concerns instead of overloading things), and still uses a vocabulary that is slightly more familiar to JS developers to help with adoption.

Interoperability is a problem. For example, by changing the behaviour of then and making it simpler, we will have to ask all previous implementors to adapt to the new specification. Abstractions built on top of those might also need to some rework. However, sometimes breaking backwards compatibility can be worth it.

@puffnfresh

Just got reminded by @dherman - anything with a then is a "Thenable" which is treated specially in the spec.

If we also put a then on Array - things would seriously break when an array is returned from a function passed to promises libraries!

There's two choices:

  • Not everything with a then method can be treated as a promise Thenable
  • The then method shouldn't do special things when value returned from the function doesn't have a then - we can't have libraries rely on that behaviour

One of these things really needs to change to allow DRY code.

@puffnfresh

To be exhaustive, here's two more choices:

  • Let promise libraries break when we add things like Array.prototype.then
  • Don't write DRY code

I know some people will reach for that last option but I don't want to give up 😄

@domenic
Member
domenic commented Apr 11, 2013

OK. So. People seem to not have understood how bad of a mismatch @pufuwozu's post was for Promises/A+, both in content and in tone. Let's try to clarify that, and hopefully make it clearer why I stand by my original post.

First, coming into a community and saying "you're doing it wrong! look over here!" in a post filled with technical inaccuracies and misunderstandings is rude and unwelcome. Compounded with well-known troll @paulmillr, of racist CoffeeScript pull-request fame, as the messenger, you can see how this is problematic.

Now, let's get down to brass tacks. Although @pufuwozu has backed off on his onFulfilled-must-return-a-single-type argument, the general perspective from which such a demand comes from is unrealistic, and indeed stems from a typed-language fantasy land where you can enforce such things and don't have to think about the possibility of receiving an unwelcome type. Indeed, his suggestion to leave returning non-promises unspecified is ridiculous for a JavaScript specification.

Furthermore, the problem isn't only a narrow typed-language worldview. It's a narrow pure-functional language worldview as well! Promises are an implementation of the monad pattern that helps to encapsulate an exception channel into the monad. That much is, I hope, obvious to all. But a key part of how promises implement this pattern is by embracing the imperative nature of the language, and automatically translating return (of non-thenables) and throw into the appropriate promise representation. Any implementation of monads that is "right" for JS will necessarily embrace these imperative possibilities, instead of ignoring them.

Finally, there seems to be a crucial misunderstanding of what the Promises/A+ spec is meant to do. It is not meant to dictate a standard for all aspects of promises in JavaScript, and it is certainly not meant to provide a standard for monads in JavaScript. Instead, it is something that grew out of many years of implementer experience, and the need to provide a minimum interoperable specification of the then method. As such much of its complexity is focused around interoperability (see the entire Promises Resolution Procedure). Remember:

A standard for sound, interoperable JavaScript promises—by implementers, for implementers.

This is why decisions like promise creation are not part of the core specification, while in contrast, we do carefully specify thenable assimilation.

I think the key here is something that grew out of many years of implementer experience, and more importantly many years of JavaScript experience. We have found an appropriate implementation of a specific monadic pattern in JavaScript, and that includes things like how we handle exceptions, how we handle polymorphic return values, and how we duck-type other implementations of the same pattern. This also feeds into discussions of flattening promise chains; it has previously been discussed over and over that a promise-for-a-thenable is a problematic pattern in JavaScript and should not be supported. (A promise for another monad, of course, is fine.)

In contrast, nobody has spent years implementing algebraic patterns in JavaScript. If they had, I think we'd be having a very different discussion right now, with much less focus around Haskell idioms and much more focus around those types of issues. The insistence of some people in this thread that the Haskell (or Scala, or...) implementations of these algebraic patterns is what we should do is very misguided.

I fully support any efforts to try to come up with some standard way of expressing monads and other algebraic concepts in JavaScript. Preferably, it should embrace the language instead of trying to make it conform to patterns of languages from other paradigms. But that's not something I am interested in spending time on as part of Promises/A+. I think a separate GitHub organization, perhaps with overlap from those who seem interested, is the right place to do such work.

In the meantime, Promises/A+ has no intention of incorporating whatever nascent "monad standard" such an organization comes up with. Neither, I think I can safely guess, does the DOM Standard and their DOM Futures.

Indeed, I think the standardize-first approach is the wrong one; it is certainly the opposite of what we have done with Promises/A+. Rather, I'd suggest going off and building libraries, with appropriate wrappers---one for Promises/A+ promises; one for arrays; one for an option type implementation; etc. If you have, after a few years of experience, managed to create a pattern that is widely used in libraries as popular as jQuery, Ember, Angular, WinJS, and more (perhaps with some flawed implementations, like jQuery's, lol): only then should you write up a spec, and a test suite. You need those years of experimentation to determine what the spec should even say.

Finally, after those years of experimentation, and the subsequent months of hammering out a standard, then come back to us. Maybe, if you've built an extremely compelling "algebraic JavaScript" ecosystem, Promises/A+ would consider a 2.0 revision in which we effectively tell all Promises/A+ implementations that the benefits are compelling enough that they should implement these new APIs as well. Most likely, if you're indeed that successful, most Promises/A+ implementations will already have adopted your patterns, and it'll just be a matter of codifying de-facto reality into something with all the edge cases smoothed out---like we have done with Promises/A+ 1.0 and 1.1.

@robotlolita

@domenic

it has previously been discussed over and over that a promise-for-a-thenable is a problematic pattern in JavaScript and should not be supported.

Except that the current behaviour of then, while allowing for compatibility with all sorts of previous work, is confusing, and rather prone to break exactly because of the overloading.

Someone returns a thing that has a then method and is fed into a Promise function? Broken. Someone use a Parser Combinator library that uses then for sequencing actions but no promises, feeds that into a promise-expecting function? Broken. How does the user reason about that? Well, he doesn't, and that's what is really terrible about Promises/A+.

Specially if Promises/A+ gain enough traction to be used everywhere as they should, consider:

var compileP = liftNode(compile) // compiles AST to something
var flattenP = liftNode(flatten) // flattens and optimises AST
var parseP = liftNode(parse) // parses text, returns AST
var source = fs.createReadStream('source.foo')
var compileSourceP = compose(compileP, flattenP, parseP)
spit(process.stdout, compileSourceP(slurp(source)))

Now, consider spit is a function of type spit :: Promise Stream, Promise String -> Promise (), and slurp is a function of type slurp :: Promise Stream -> Promise String. parse, flatten and compile are straight-forward in meaning, but they are not functions that know about promises, they are just wrapped in a function that transforms the arguments into a Promise, applies the function, and wraps the return in a Promise.

The problem now is all of those functions need to use then to wait for a promise to be resolved. Let's say the AST happens to have a then method for whatever reasons, this means we wouldn't be able to write that code, because flattenP would try to assimilate the return of parse, which is not a Promise.

See also @pufuwozu's comment above yours.

Is @pufuwozu filled with Haskell's dogmatism? I don't think so. Could he have worded his proposal better? Perhaps. Does he raise good points that we should evaluate? I think he totally does. Even if we disregard the whole "monads-category-theory-functional-programming-narrow-minded-pure-theoretical-ideas", there are some problems with the specs right now, which will mean things are gonna be fucked up as people begin to use them more and combine them with regular values. This is made worse by JS's lack of type constraints.

Indeed, I think the standardize-first approach is the wrong one; it is certainly the opposite of what we have done with Promises/A+.

It's also one of the things that bring in some terrible incidental complexity because now you have to support all of the previous work on this front. Same happened with ECMAScript. And is happening with ES6 again. Accidental complexity is never something we should strive for, imho.

As a side note, the tone of this discussion is really not helping. Fights means people get more defensive and less open to other people's ideas, which is, I believe, not what we want here(?).

@puffnfresh

@domenic my implementation of point is wrong - where are my other technical inaccuracies and misunderstandings? You haven't commented on them.

... and it is certainly not meant to provide a standard for monads in JavaScript.

It's not - but let's at least make it compatible! What was the reason not to?

Furthermore, the problem isn't only a narrow typed-language worldview. It's a narrow pure-functional language worldview as well! ...

That whole paragraph has nothing to do with what I've been talking about. The API that I derived is not pure at all. Thinking that this has anything to do with purity is absolutely wrong. I have no idea where you got that idea.

I think the key here is something that grew out of many years of implementer experience...

You can't just say "we've spent years implementing X, so it doesn't have to do/be Y" - that's absolutely useless. Give a reason, not an argument from authority.

... and more importantly many years of JavaScript experience.

I have been writing hobby JavaScript for 11 years - professionally for quite a few of those years. I write a few serious JavaScript libraries. I'm not coming in here from outside of the JavaScript community (not that it should matter, at all).

I also use many other languages - does that make your JavaScript experience more important or something?

We have found an appropriate implementation of a specific monadic pattern in JavaScript, and that includes things like how we handle exceptions, how we handle polymorphic return values, and how we duck-type other implementations of the same pattern.

They're not monadic - that's the whole point. What you're doing could be monadic - it's very close!

Preferably, it should embrace the language instead of trying to make it conform to patterns of languages from other paradigms.

Why do you have this crazy idea that because Haskell recoginises that monads are monads, then we're trying to make JavaScript like Haskell? You're being very dishonest.

The API that you can derive makes sense algebraically. This has nothing to do with Haskell or Scala.

In the meantime, Promises/A+ has no intention of incorporating whatever nascent "monad standard" such an organization comes up with.

Useless.

Finally, after those years of experimentation, and the subsequent months of hammering out a standard, then come back to us.

Wait, so is it absolutely no or maybe?

Come on. I read all of that for nothing - there's no content!

I have not seen any reasonable excuse against recognising that promises are monadic. You've basically come up with "it's pure" (incorrect), "it's from Haskell" (incorrect), "it's not from JavaScript" (that's what I'm trying to fix) and "implementors made this - we don't need your input" (urgh, thanks).

Anyway, I'm going to reiterate our 4 choices:

  1. Use something other than checking then
  2. Remove then which does special things for when returned with no then
  3. Let the promises that implement this spec break when used with DRY code
  4. Don't write DRY code

Yes, all the options suck. Problem is that I think #4 sucks the most.

@ForbesLindesay
Member

@killdream the issue of things which have .then methods but aren't "thenables"[1] is vastly overstated. I haven't seen a single issue on GitHub about a bug resulting from this behavior. Given that I work on projects that use promises (both my own and other people's) almost every day, I would've seen hundreds if this was a significant problem. It just isn't.

[1] thenable: (something close enough to a promise to be successfully assimilated

@puffnfresh

@ForbesLindesay I added a then method to Array. Everything broke. Want to file an issue for that?

@ForbesLindesay
Member

OK, I've now seen one person with a problem resulting from this, and you extended the prototype of a built-in. There are exactly 2 reasons why that would be acceptable:

  1. You're experimenting with an API you hope will be considered for a future version of JavaScript. What you've just demonstrated is that adding such a method would break lots of websites, so it won't be added. That concludes your experiment.
  2. You're shimming functionality that is already either in at least one JavaScript environment (browser or node.js) already or has been specified within a standards body with sufficient backing. The promisesA+ standards body doesn't yet carry that kind of weight, you most certainly don't.
@puffnfresh

@ForbesLindesay urgh, missing the point.

I just wrote a wrapper around Array and added a then method. Everything broke!

@robotlolita

@ForbesLindesay I'm okay with assimilating promises when you return a promise from a function in an on{Fulfilled,Rejected}, I'm not okay with assimilating things with a then function. That it hasn't happened until now doesn't mean it will never happen (potential ≠ guaranteed). For example, null is a terrible API design, everyone agrees with it, but even so null references are only "potential" problems.

Specially problematic with things that looks like thenables but aren't quite. Duck-typing is not the way to go here because the semantics of a Thenable are really important for them to be assimilated successfully, and these kind of errors might not exactly be early errors, which is more of a problem because now you have to step through your whole code base to figure out what went wrong. Dynamic branding would be a better option to mark compliant promises specifically and only assimilate those. Then the user has to go through all the trouble to wrap foreign things that are close to promises, which ensures they won't shoot themselves on the foot by a seemingly innocent code like the one in my example above.

@puffnfresh

@ForbesLindesay I wrote this:

function Id(a) {
    this.value = a;
}
Id.of = function(a) {
    return new Id(a);
};
Id.prototype.constructor = Id;
Id.prototype.then = function(f) {
    return f(this.value);
};

Everything broke!

@rtfeldman

@pufuwozu seems to be arguing that Promises/A+ ought to be compatible with monads because it only requires a small change and doesn't have any significant drawback.

@domenic seems to be arguing that the drawback is insurmountable, but I'm not seeing where this is demonstrated.

Indeed, his suggestion to leave returning non-promises unspecified is ridiculous for a JavaScript specification.

Why is this ridiculous? jQuery doesn't specify what happens when you pass a hundred arguments to $(foo).siblings - does it need to? JS is a dynamic language. There are all sorts of unspecified legal actions; as JS authors, we are used to that.

Compounded with well-known troll @paulmillr, of racist CoffeeScript pull-request fame, as the messenger, you can see how this is problematic.
I think the key here is something that grew out of many years of implementer experience, and more importantly many years of JavaScript experience.

Serious question: when people argue with you using ad hominem attacks and bludgeoning you with their credentials, does that make you more likely to think that (A) they are probably right after all, or (B) they are digging in their heels for pride rather than what's best?

Please reconsider.

@erights
erights commented Apr 11, 2013

@killdream "Let's say the AST happens to have a then method for whatever reasons," Don't do that.

The original E promise API which inspired all these others has no assimilation. When I first heard about assimilation, I thought it was a terrible idea, for many of the same reasons that underlie many of the above objections. However, as I saw the JS promise landscape evolve, I despaired more over a worse problem:

Several similar but different elephants in the room: jQuery promises, WinJS promises, Q promises, and DOMFutures. All of these but DOMFutures have enough installed base that they're not going away. And re DOMFutures, never underestimate the ability of the w3c to be an elephant even for premature "standards". Much code will have to be written in an environment inhabited by multiple such promise systems simultaneously. If none of these recognize each other's promises as anything promise-like, then programmers will face the burden of dealing with a "jQuery promise for a Q promise for a WinJS promise for a DOMFuture for a number". Call it the JQPFAQPFAWJPFADFFAN problem. I didn't see how we could get from that situation to one with an agreed standard promise anything.

Fortunately, the starting point for all of these elephants was so similar that the Promises/A+ process was able to tease out and codify a common-enough ground for all of these to agree on. This is messy real-world standards work; as much politics and sociology as technology and math. Given the constraints imposed by legacy, we should all be overjoyed that the Promises/A+ community has been able to extract something as beautiful as they did. But even if they all agreed on exactly the same spec, so long as jQuery promises only recognize jQuery promises as promises, and likewise for the others, we would still have the JQPFAQPFAWJPFADFFAN problem.

Had ES6 with its unique symbols happened long before any of these promise libraries, we could have used a unique "@then" symbol for assimilation and so avoided the accidental collision @killdream worries about above. Likewise, if all of these had stuck with the E (or original Q) name "when" rather than renaming it "then", we would have less accidental collision, and would be less irritating to category theorists. (FWIW, I think "when" was a much better term than "then" for this anyway. I am still unclear on the history of how this got renamed.) Alas, it didn't turn out that way.

Given multiple promise systems that each recognize only their own promises as promises, but which mostly agree on the meaning of "then", assimilation turns out to be a surprisingly pleasant way for these to co-exist. Given the actual situation we have to start from, having this assimilation be based on duck typing on the presence of a function named "then" is unfortunate, but there was no other practical choice.

@ForbesLindesay
Member

@rtfeldman the comparison with jQuery is completely out of place here for two reasons:

  1. jQuery sucks at things like this, check out their broken promises.
  2. jQuery isn't a spec, so it's completely different.

A fair comparison would be the ECMA Script spec, which almost always specifies these corner cases (the only reason it would ever not do so would be by mistake.

@ForbesLindesay
Member

@pufuwozu Your Id object:

function Id(a) {
    this.value = a;
}
Id.of = function(a) {
    return new Id(a);
};
Id.prototype.constructor = Id;
Id.prototype.then = function(f) {
    return f(this.value);
};

Looks plenty enough like a promise to me, when I assimilate it using a true Promises/A+ library I get a promise which resolves to the value of the Id. That's well specified, well defined behavior. I've found it helpful hundreds of times, and never found it a problem, and nor has anyone else.

@killdream

never happen (potential ≠ guaranteed)

Yes, but JavaScript is not a language that concerns itself greatly with proving things. Pragmatically speaking, I don't think it will cause anyone problems unless they are trying to abuse promises to make them more like Haskell Monads, as such, I don't think it's a problem. We considered "branding", but that ultimately causes the same problems, just one down the line: "It doesn't matter how many _s you add to the start of a property in JavaScript, it's still public".

@rtfeldman

@ForbesLindesay Fair point that jQuery isn't a spec.

Here's the ECMAScript 5.1 spec on Array.prototype.reduce: http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21

  1. If len is 0 and initialValue is not present, throw a TypeError exception.

This is the ECMAScript spec saying "if you pass an empty array and no starting value to reduce, that doesn't make sense, so TypeError."

Granted, explicitly specifying "throw a TypeError" is not the same thing as leaving it unspecified, but this is certainly an example of a JS spec saying "you can't use certain types in places where they don't make sense," no?

@erights
erights commented Apr 11, 2013

@rtfeldman effectively saying "you can't use certain types in places where they don't make sense," by deterministically throwing a TypeError is fine. Unspecified isn't. Care to rephrase what you had in mind in as a deterministic spec?

@wizardwerdna

I'm not certain that I understand the problem here. As understood, the following are equivalent:

p.then(onFulfill, onRejected)

and

p.then(onfulfilled, undefined)
p.then(undefined, onRejected)

indeed, many implementations provide single-function shorthands. And of course, a full two-argument could be built from those single-function shorthands, thus

function then (onFulfill, onReject){
  p.shorthandFulfill(onFulfill)
  p.shorthandReject(onReject)
}

It would seem, then, that nothing in the Promise/A+ spec precludes the use of a promise as a monad, and the monadic promise proposal does not preclude the use of a promise as a practical notation for asychronous behavior. Given that Javascript and the spec provide that .then(oneCallback) operates the same under each proposal, the debate seems unnecessary.

Why choose?

The question is only how the notation for the spec should be accomplished. I agree with the overarching sentiment that we should describe the specification, so much as possible, using notation that users and developers understand. In this way, clients can read the spec and comprehend what is happening. Category theorists can use the spec trivially and without translation. And there is no reason why it can't be pointed out that distinct uses of the .then functions result in monadic behaviors.

The .then position is only one of a few pieces of the proposal, but I simply don't get this piece at all. I will write separately about the other issues.

@robotlolita

@ForbesLindesay @pufuwozu's example breaks depending on the assumptions the developer has when he wrote the Id constructor. If he doesn't expect it to be assimilated by a Promise library, it's broken.

@erights Legacy and incompatible things are unfortunate, which was I said all that stuff about accidental complexity. I believe explicitly wrapping non-conforming implementations would be best. Have a function like promiseFromThenable(a) :: Thenable a -> Promise a the developer can use to explicitly glue alien functions together, but I haven't really considered edge cases.

My example with the AST was based on the assumption I don't control that library. @polotek seems to have a Node library with a then method that isn't a promise. Promise-expecting functions can't use that library as a result.

@ForbesLindesay I actually think the probability of these kinds of things increase as you use Promises as actual Promises, because then you'll be mixing them with all the sorts of regular values, and it won't be apparent from your code base what is a promise and what isn't. If I omitted most of the definitions of my example, it would be one of those cases:

// is `source` a thenable? Is the result of `slurp? The result of `parse`? The result of `flatten`? ...
spit(process.stdout, compile(flatten(parse(slurp(source)))))
@erights
erights commented Apr 11, 2013

@polotek Please fix your Node library. Thanks in advance.

@killdream yes, until @polotek fixes his library, it can't co-exist with various promise libraries. It is far too late to do anything to fix that. The evolution of JavaScript has always been and remains constrained by its history of usage.

@wizardwerdna "p.then(onFulfill, onRejected)" evaluates to a promise for the value that the invoked callback will return. What does "p.then(onfulfilled, undefined); p.then(undefined, onRejected)" evaluate to?

@ForbesLindesay
Member

@killdream

I use promises is in all sorts of large real world applications. Some of which are closed source, but there are some notable exceptions such as esdiscuss and the now deprecated component-website. You just don't end up suffering these issues.

As for your idea of forcing people to explicitly convert thenables into "Promises" it simply doesn't work. There is no sure fire way to tell the differences between a Promises/A+ promise and a thenable. Branding would get close, and was considered, but ultimately rejected. Search through the issues if you want to see more.

@wizardwerdna

There's a slight problem, you're neglecting the return value of then. p.then(onFulfilled, onRejected) is actually equivalent to:

(p will be fulfilled) ? p.then(onFulfilled, undefined) : p.then(undefined, onRejected)

Unfortunately p will be fulfilled is uncomputable

@wizardwerdna

Another piece of brian's article goes to his "point" function. This functionality already exists in several libraries, typically named "coerce" and sometimes referred to as "assimilate". Indeed,the newest spec requires an "abstract procedure" called Promise Resolution Procedure, which is often manifest in code as resolve(thingie). This is nothing more than exposing resolve to the API. On the other hand, since it can easily be coded from the already-specified API, why does it matter?

Indeed, let me ask this question. Since the monadic use of Promises/A+ can be built completely from the spec, why not consider it simply to be a mixin that makes a Promise into an MPRomise?

@wizardwerdna

erights, you got me. How about

p.then(onFulfilled, undefined).then(undefined, onFulfilled)

This returns a promise for the state and value that the invoked callback will return, albeit through a chain of promises via the Promise Resolution procedure.

@ForbesLindesay
Member

That one is much more obviously different. Consider what happens if onFulfilled throws an exception.

@wizardwerdna

The nonstarter in Brian's proposal, in my view, is the following:

I think then should only take the onFulfilled function and it should have unspecified behaviour if the function doesn't return a promise (for simplicity; map can be derived).

I simply don't think that we are all that hung up on types here in the land of javascript, and the /A+ specified behavior seems intuitive to me. Since Brian doesn't care (his definition permits the existing usages), the /A+ spec seems to do what he wants, just more.

@puffnfresh

@wizardwerdna well, it's tricky to explain.

If we have other things that have then because they're all monads - then adhearing to the promises spec promises that you'll be stuff when I pass a monadic Array in.

If the promises spec doesn't specify that it should fall back to map, then some libraries can reject that behaviour and give early errors.

It's not about types.

@wizardwerdna

@ForbesLindesay

Perhaps I was unclear. Considering the chained expression, as compared to the two-parameter version, doesn't the return promise in each case end up rejected with the error of the onFulfilled call.

@Twisol
Twisol commented Apr 11, 2013

@wizardwerdna: In your chained example, if the onFulfilled callback fails, then it will be called again, but with an error instead of whatever was originally passed.

@wizardwerdna

@pufuwozu Brian, trying to follow. Can you give an example? As understood, you can still wrap the old library behaviors with a coerce function.

Invariably, you will ordinarily try to use just one library anyway, resorting to other objects only when, for example, an I/O or Ajax library returns an ugly out-of-library "thenable". But its a trivial matter to use the coercion procedure for any return value, or better yet, to define a library-version of the external API calls, returning "legit" promises.

My point is that the present Spec seems multi-purpose, and the limitations on their use proposed by Brian seems easily built atop it. If I am not wrong, what's the beef?

@wizardwerdna

@Twisol OK, I get it. The two-parameter version will only ever call the first callback, and the then-promise rejects with the error. The two-step version, errors out, so the then-promise rejects with the error, triggering the second callback. This is a semantically important consequence of the one-callback-only limitation of the spec.

Thus, I agree that we don't have equivalence, in that the monad can't seem to readily reproduce the A+/spec version. But doesn't it work fine the other way, in that the monadic version can be replicated by the spec? Thus, why the sadness in the monad community, since the spec already gives them what they want. Defining monadThen as function monadThen(cb){specThen(cb);} and monadOnRejected as function monadOnRejected(cb){specThen null, cb);}

@puffnfresh

@wizardwerdna so here's my "monad spec" for JavaScript:

A monadic value is something that:

  • then takes a function which takes a value and returns a monadic value of the same constructor - then also returns a monadic value of the same constructor
  • constructor.of creates a monadic value of the same constructor

It also has to obey these laws:

  • of(a).then(f) is the same as f(a)
  • m.then(of) is the same as m
  • m.then(f).then(g) is the same as m.then(function(x) { return f(x).then(g); })

Great, promises implements part of this specification (and hopefully more, soon). Awesome.

I make my Id constructor implement the above monadic specification - now I pass it to a promise library. Uh oh, it also has then so promises thinks it's a Thenable and does strange things with it. It's buggy.

It's not about promises not being part of "the monadic spec" - it's that promises will break when there's more than just promises involved.

Should I move forward with implementing many things with then and just let promises break? Or should promises change to not let this happen?

Does that make sense now?

@erights
erights commented Apr 11, 2013

@pufuwozu Don't define then methods that don't conform to the Promises/A+ spec. If you want a then-like method with a different behavior, call it something else.

@puffnfresh

@erights but Promises/A+ already provides a compatible then method. They are close to monadic. It's a problem with promises if they do strange things with other monads. They shouldn't.

@robotlolita

It's really sad that @erights highlights how tied we are to a stripped version of structural typing where every superset of { then :: Function } is considered a Promise =/

@Twisol
Twisol commented Apr 11, 2013

@wizardwerdna: Yes, that's right. Since the 2-ary then is strictly more powerful than then and otherwise, you can implement both of them in terms of the former. I do think that the 2-ary then should be called something else, but that ship sailed a long time ago.

The thing is, there are other things in Promises/A+ that you have to be careful of. Despite @pufuwozu's statement that "it's not about types", there are a lot of design tradeoffs based on the very fact that you can't differentiate types beyond a certain level of complexity. Have a single type you want to handle specially? Great, use instanceof. Have a whole range of types that have to implement an interface? That kind of check, in Javascript, ends up with problems.

And unless you want to use a transpiled Javascript language, there are no good solutions to the problem which also keep things at an approachable level. I admire @pufuwozu's approach to functional techniques in bilby, but it forces a total paradigm shift that most developers won't want to deal with. We could refactor then into separate bind- and fmap-alike methods, but in JavaScript that just makes them more brittle.

@pufuwozu:

It's a problem with promises if they do strange things with other monads. They shouldn't.

If we had a stronger type system, the Promise methods wouldn't even accept other monads as input. Functions that are total in statically typed systems are partial in JavaScript - and we don't even have a good way to restrict the domain manually. It sucks. 😦

@bradphelan

@Brian could you explain what strange things might happen in the context of
some other real monads such as option/maybe and a reactive pattern monad.
What does strange look like to a developer running across a problem.
On Apr 11, 2013 7:46 PM, "Brian McKenna" notifications@github.com wrote:

@erights https://github.com/erights but Promises/A+ already provides a
compatible then method. They are close to monadic. It's a problem with
promises if they do strange things with other monads. They shouldn't.


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

@erights
erights commented Apr 11, 2013

@killdream By "stripped version of structural typing", if you mean, "duck typing", yes. And yes, it is sad. There are many sadder things that we live with because of historical constraints.

"every superset of { then :: Function } is considered a Promise =/". No. every superset of { then :: Function } is considered a thenable. That's an important difference.

@Twisol
Twisol commented Apr 11, 2013

"every superset of { then :: Function } is considered a Promise =/". No. every superset of { then :: Function } is considered a thenable. That's an important difference.

Is there somewhere that describes exactly what semantics a Thenable is expected to have? I've taken to calling monads in Javascript "Thenables", and I'd like to know if I'm reading into it too much.

@robotlolita

@erights well, it doesn't make much of a difference in this case, as it's more of an issue with them always being treated as something really close to a Promise in semantics, such that you can safely assimilate them.

@Raynos
Raynos commented Apr 11, 2013

It should be pretty obvious that interop with anything but promises is 100% out of scope for the promises spec and the promises community.

@puffnfresh

People still wanting examples:

// I'd implement this differently but I'd have to explain
function Optional(a) {
    this.hasValue = typeof a != 'undefined';
    this.value = a;
}
Optional.of = function(a) {
    return new Optional(a);
};
Optional.none = new Optional();
Optional.prototype.then = function(f) {
    if(!this.hasValue) return this;
    return f(this.value);
};

Let's write a single function to add my last name to both promises and optional values (assuming promises have constructor.of):

// Helper library
function map(x, f) {
    return x.then(function(a) {
        return x.constructor.of(f(a));
    });
}

function addLastName(m) {
    return map(m, function(name) {
        return name + ' McKenna';
    });
}

Promises in application.js:

addLastName(promisedNmae)
    .then(function(name) {
        // Brian McKenna
        console.log(name);
    });

Optional values in test.js:

addLastName(Optional.of("Brian"))
    .then(function(name) {
        // Brian McKenna
        console.log(name);
    });
@wizardwerdna

@pufuwozu Thanks for all the time and attention you have given to providing this argument. My points were not to say /A+ promises were close enough, but rather that it seems that an A+ promise can be wrapped into a monadic value as you describe it. And in that sense, it may well be that the spec is "good enough" for both communities, the promises community that is focused on what seems a more intuitive (and powerful) interface focused on a particularized use case.

I think the point made by @Twisol is that the /A+ promises functionality of a two-parameter then cannot be identically reflected from a pure monadic promises implementation, but the pure monadic implementation can be built from the /A+ specification. This suggests to me that the status quo can be resolved simply with /A+, and a monadic wrapper surrounding it. Clearly, promises folk can't rely on libraries depending on monads without wrapping, but once wrapping they can have all the benefits, with the corresponding costs.

Where am I dropping the thread here?

@Twisol
Twisol commented Apr 11, 2013

I think the point made by @Twisol is that the /A+ promises functionality of a two-parameter then cannot be identically reflected from a pure monadic promises implementation

Just to clarify: The presence of a more powerful function doesn't mean that it's somehow not a "pure" monad. The promise wrapper you mention could easily expose a 2-ary then-like function; it's just outside the scope of what a monad requires. (See Haskell's Either and its either function, they're pretty directly analogous.)

@polotek
polotek commented Apr 11, 2013

@erights it's interesting that polotek/procstreams has been brought up a few times when people start talking about promises/monads, etc. I haven't been following all of the debate. I don't have that much of a vested interest. But since you've addressed me directly, I'll respond here for future reference.

Asking me to "fix my library" presumes that you've decided it's broken. It also presumes that I would agree with you that it's broken. My library works as designed for the most part. It's even got some tests on it (non-exhaustive as they may be). The point of contention here seems to be my use of the names "promise" and "then" in my design. Before I respond to that, let me say I actually really like the work done here with promises A+. Not because I subscribe to all of the ideas here. But because I love to see folks working together to improve their corner of the world. It's great work, and when/if I ever pick up a promise implementation, I'd take a second to check if it was A+ compliant. Because I think you all have put a lot of thought into things.

That said. I think it's a bit disingenuous to suggest to me and everyone else who ever uses the names "promise" and "then" that they should conform to A+. Even considering that I agree with what you're trying to accomplish here. There are several reasons for this. One is the presumption that I share your goal of interoperability. "until @polotek fixes his library, it can't co-exist with various promise libraries". Procstreams weren't designed to co-exist with promise libraries. No one has asked for it. I'm not sure how it has somehow become a requirement for your design, or how subsequently fixing that is my problem. Or maybe I'm misunderstanding your point. Are you saying that the promises A+ spec cannot and should not try to accomodate all Thenables in js? Because that I agree with.

I appreciate folks here for thinking of me. But procstreams probably won't change. Not because I'm a jerk. But because I like the way it is now. And more importantly, no one here has bothered establishing a connection with me that would make me inclined to accomodate you. If the plan is to effectively convert all Thenables in all libraries to support A+, this may come up a lot. Pulling people into random arguments and telling them to fix their code probably isn't the best tactic.

@erights
erights commented Apr 11, 2013

@polotek "Pulling people into random arguments and telling them to fix their code probably isn't the best tactic."

Hi Marco, you are correct. I apologize. I really like your clarification and agree with much of what you have to say.

@robotlolita

@tonymorris while @domenic's words weren't the best ones throughout this thread, I don't think we need any more fighting here.

@erights
erights commented Apr 12, 2013

@domenic Is there an accepted way to banish someone from further posts? I admin some email lists. After a post like @tonymorris's, I would banish that person, probably forever. But the mechanics of discussion on Github are new to me.

@erights
erights commented Apr 13, 2013

@kennknowles Hi Kenn, thanks for your review of the history. I know much about the history of promises but much less about the history of monads. On their intersection, I don't know of anything earlier than 2008. If you do find any earlier expositions, I would indeed be very interested.

While promises/futures as a whole do indeed have a long history, the comparative novelty (mid-to-late 1990s) relevant to JavaScript promises is
a) the use of non-blocking promises in the context of communicating event loops, and
b) that the callback registration (by "when" aka "then") returns a promise for what the callback will return.
Yes, #b is much of why they currently look monad-like. But this idea was proposed on e-lang by Mark Seaborn and first incorporated into E in, IIRC, the late 90's. This was well before we had any hint of a connection to monads.

Prior callback-registration systems always had the registration return void/null, and so the purpose of the callback could only be the side effects it performs. As may now seem obvious to monad folks, with this innovation we could use promises to rearrange operational time while still staying within a mostly functional style. Tom Van Cutsem's thesis at http://soft.vub.ac.be/~tvcutsem/publications/phd_tom_van_cutsem.pdf explains all this rather well.

@kennknowles

@erights Very cool! Thanks for the additional references. Let us definitely hope that these various threads can all be leveraged. I'm afraid I don't have time to reference chase, but I would start with something from Alice ML (~2005) or A monad for deterministic parallelism (2011) and work backwards.

@joseanpg

@erights @kennknowles: could this be the original Mark Seaborn's proposal?

Suppose the protocol is changed: Rather than just sending P, Bob also sends an arrowtail connected to P and an arrowhead that Alice will connect to be an observer for B. Bob keeps the arrowtail to Alice's observer for P. Soon after sending this message, Bob finds out that P resolved to X. Bob notifies Alice of this by sending a message to Alice's observer for P.

http://www.eros-os.org/pipermail/e-lang/2001-July/005516.html

@erights
erights commented Apr 13, 2013

@joseanpg Thanks for trying to find it! But no, that's not it. That one is about the protocol and is interesting. But the message I'm thinking of is something else. Sorry I don't have anything more helpful to offer for tracking this down.

@braveg1rl

Hey guys.
What a great discussion has been going on here. I love reading all the different perspectives. To be honest, I never really grasped the fundamentals of functional programming, and I still don't. Maybe I just have to "pay up" sometime to really . What I can say is that I definitely like the elegance of the composibility displayed in @pufuwozu 's code examples. It's all the more intriguing when thinking some (or all) of the code does not apply to his conception of promises but to other monads too. Although I wish I knew what these other types of monads could be.

WRT to the actual proposal in this issue, I do side with the "conservatives" here. I think that generally speaking, what's there now is good. At least I know I can build really cool stuff with it. Also, I think the benefits of "DRY" when seen in the context of open source ecosystem may be overstated. There is such a big "work force" out there, that you practically only have to wait for the functionality you want to appear (that is, on the level of functions, libraries). I think DRY mostly applies to code you write yourself (either individually or as a team, and where costs need to be accounted for). If you apply DRY, you'll save yourself time and headaches.

I of course would love to see more work from @pufuwozu and similar-minded people. Code and prose from them greatly challenge me, which is always a good thing.

About half-way in the thread, @juandopazo mentions an idea for an ArrayPromise, and that rang a bell for me as a simple "OO" programmer. I of course have made the jump to Javascript and first-class functions a while ago, so I have picked up some of the possibilities of first-class functions (and lost the urgence of putting everything inside a class). Recently I read an article from @jcoglan http://blog.jcoglan.com/2013/03/30/callbacks-are-imperative-promises-are-functional-nodes-biggest-missed-opportunity/ , and that lit a spark for me a few days after. (It had to sink in I think).

In any case, I've now kind of set out to explore the full potential of promises more. My end aim is to make a full alternative to dealing with "real" values, instead dealing with unresolved (promised) values up to the point where the value is actually needed. This in-between state (I think predominantly useful for readable "scripts" in node.js, because there will be quite a performance hit) I dubbed "promise land". And as a "land", it must be truly livable. I'm trying to cut it up in more manageable pieces (and also, reusable components) because not everyone will be as charmed by promise land as I am.

So currently I'm working on the following:
https://github.com/meryn/promised-builtins . This is like @juandopazo 's idea, but with the noun and adjective reversed. I'm more interested in the values (even though they're virtual, promised, simulated, however you call it) than the abstract concept of a promise. I think if I could have it my way, there wouldn't be a need for promises at all, because I just want to write beautiful code, dealing with concrete stuff (builtins, and composite "business" objects).
https://github.com/meryn/faithful (alternative to Async library, which I currently rely on for most of my "batch" work)

All in all, I want to stress that this for me is primarily a code experiment. I just want to see where it leads me, and if coding in "promise land" is a good experience. Also I want to see what the performance implications of big chains of promised values are, compared to regular node callback style.

Note that all examples are in CoffeeScript, but everything works in JS as well.

If anyone's interested, I of course would love to collaborate, because the whole of "promise land" is quite a big thing to build.

@tonymorris

Hello Meryn,
If you are serious about learning functional programmer proper, there are many of us willing and waiting to help you, including @pufuwozu. Let me know so you can be directed to the appropriate forum.

@braveg1rl

@tonymorris
Well yes I'm serious, but I'm also lazy. Just being honest. I have only had one year of academic computer science (where it would probably have been forced upon me in later years), and up to now, no material I've stumbled across was accessible enough for me to like it. I mean, laziness for a programmer is all relative. I guess I want to say that up to now I haven't been willing to bore myself to learn it. I don't believe learning it would have to be boring in principle, but I just have not stumbled upon the right opportunity yet.
In short: the learning curve has been to steep. I managed to learn OO programming gradually. I think imperative programming comes way more natural to most. i.e. Just instruct your virtual servant what to do.

But I'd sure love pointers.

To be clear, I consider myself to be almost 100% self-taught. And I tend to figure out anything by myself. I don't ask questions usually. FP raises questions for me, that I never been able to answer in "satisfactory" time.

@braveg1rl

I must also add that at this point, I don't know if learning FP per se can really "help me". I've firmly settled on Javascript as language platform of choice, and I think that in light of "transparancy", I don't want any source-to-source compiling language to stray too far from Javascript semantics. What CoffeeScript does now is about the sweet spot for me.

I see two things in how it could benefit me:

  1. Inspiration for powerful functions to be part of "promise land". Perhaps seeing certain pitfalls earlier.
  2. Knowledge for later, to maybe perhaps realize my ambition of doing an own programming language. I care about aesthetics and elegance a lot, so I I grasp on to any opportunity to get things more "right". Right now, I want my own code to be totally DRY, so if I'd extrapolate that trait of me to potential language design, then I want my language to support me in gettings dry, hence offering the best kind of primitives to achieve that aim. And I know "under-exposure" to FP results in a blind spot for me. I of course have no idea of how much I'm missing out on. And even if what I'm missing isn't huge (or lots), then still, I want it all. :)
@braveg1rl

One thing that appeals to me about "promise land" is that it will be 100% at home in Javascript (and CoffeeScript). If I just wanted to make my code look more or less like synchronous code, I good go for something like IcedCoffeeScript. I can also imagine that there could be ES6 to ES5 transpilers which support the "yield" keyword. And then use something like task.js. Yet, I think it would result in awfully messy code, not in the least resembling what you wrote yourself.

@puffnfresh

@meryn do-notation in Haskell or for-comprehensions in Scala. Makes async code look synchronous. Also works for handling of optional values, list comprehensions, state, IO, reading configuration files, writing to logs...

@ForbesLindesay
Member

Yep, most transpilers aren't nearly good enough to use in real application code. Some of the ES6 ones are getting there with the addition of source maps etc.

The long term goal of promises is to make writing asynchronous code as easy as possible.

I think you slightly miss the point of DRY by saying it doesn't matter as much in open source. It's not just about not writing the thing multiple times. That is to say it's not just about saving time writing. It's largely about bug fixes. If you don't repeat yourself, then fixing a bug in one place fixes it ever. Arguably that's more important in the open source world because if it's your own code you know what needs fixing.

Having said why DRY is important, let me tell you why it isn't. It's important for code to be easy to read. If a function is too "clever" it might become impossible for a new-commer to interpret. You might think your being very clever by having one function that can double every number in an array or double the value represented by a promise depending on what's passed to it, but that will likely just make your code harder to interpret.

People will just start saying "so what's this function intended to take as an argument, an array or a value." when they're looking at the function's source. And when they're looking at a value being passed to the function they're liable to say "what does that function do, does it expect a promise for an array? Is it going to treat my number as an array of bits and double each one, or does it just treat it as an array of length 1 and double that one number. What will it return, a new promise or a new array.".

You need to try and write code that doesn't raise lots of questions. That's why you want to be careful when using one representation for lots of largely unrelated things. The key thing to always keep at the back of your mind when writing code is that you'll write it once, and it'll probably be read hundreds of times (on average).

@braveg1rl

I think I saw this do-notation mentioned a while ago. At that time, I thought it could have better been called "await" or so, because if I understood correctly, it halts execution of the "series of statements" in that function right there, up to the value has been resolved. I think it's much alike to what task.js uses ES6 yield for. I like that. I wish we had a billion browsers with support for that. :)

@ForbesLindesay
Member

Yep, ES6's yield (with task.js) is probably most similar to C#'s await.

@braveg1rl

My idea for promise-land is to translate as much of that syntax into function and/or object "compositions", in effect making the resulting "language" (you'd lose almost all regular language constructs after all!) a tiny bit "lispy". Note that this idea becomes a whole lot more alluring if you have CoffeeScript in mind, not Javascript. I'd go nuts over all the function()'s, braces and parentheses everywhere.

@robotlolita

Nope, do notation is just sugar for transforming this:

do
  text <- readFile "foo"
  writeFile "bar" text

Into:

readFile "foo" >>= writeFile "bar"

Or in JS terms:

readFile("foo").then(function(contents){ return writeFile("bar", contents) })
@braveg1rl

@ForbesLindesay And Haskell's do is similar to that, right? Otherwise, I have misunderstood.

@killdream Our respective comments crossed each other.

@braveg1rl

Oh, that seems like @michaelficarra back-arrows for CoffeeScript (or whatever he called them). Now I know where he stole them from. ;)

@erights
erights commented Apr 15, 2013

@ForbesLindesay said "Yep, ES6's yield (with task.js) is probably most similar to C#'s await." See also http://wiki.ecmascript.org/doku.php?id=strawman:async_functions and Kris Kowal's adaptation of this in Q to current FF generators. I would argue this is more like C#'s await than task.js is, since it does not make tasks a subtype of promises.

@puffnfresh

@meryn yeah, @michaelficarra is a closet Haskell lover. JavaScript is fun but he'd use Haskell for real world programs 😉

@braveg1rl
readFile "foo" >>= writeFile "bar"

In this code, how do any potentials errors flow? Would they be "caught" by a try catch block wrapping the entirety? Node-style callbacks require explicit "error-propagation".

Or maybe the more relevant example is this:

do
  text <- readFile "foo"
  writeFile "bar" text

Where would any error go to?

@ForbesLindesay
Member

@killdream yield when used with things similar to task.js (or the Q implementation) is essentially syntactic suggar for turning:

...expression start.... yield promise ....expression end....
...subsequent statements...

into

return promise.then(function (__uniqueVar__) {
  ...expression start.... __uniqueVar__ ....expression end....
  ...subsequent statements...
});

Except for the fact that it can handle all JavaScript flow-control constructs. So it works exactly as normal with try/catch, while loops, for loops etc. It also does the correct thing even with conditional expressions like:

var value = tryGetFromCache() || yield getFromServer();
var value2 = getFresh ? yield getFromServer() : getFromCache();
@braveg1rl

Ehm, in my mind, syntactic sugar is something that actually translates to (less-friendly, less-sweet..) syntax. But if I understand task.js correctly, it doesn't do any code generation, right? I must say I have not studied it in depth. Just read through the examples a bit. I saw task.js as a kind of creating a "controller-object" which with some work make yield suit the purposes of the promise-community (instead of for iterators, where yield seemed to have been designed for).

@ForbesLindesay
Member

Yes, it's not doing a transformation like that, but it's a nice way of thinking about it. I have a project QJS which attempts to implement yield as a source-to-source transformation.

I think you slightly mis-understand syntactic sugar though. Syntactic sugar just means that it's new syntax to make something easier to write, but doesn't provide new functionality as such. Anything you can write with yield + task.js you could write without it, it's not a new language feature as such, just syntactic sugar.

I find it helpful to understand it like that, because that way it doesn't seem like magic. I don't like when things seem like magic, because that's when they tend to break in unpredictable ways (e.g. browserify@version1)

@ForbesLindesay
Member

In case that wasn't clear, yield is syntactic sugar provided by Ecma Script 6. Just because it's provided by the language and not a transformer, doesn't make it not syntactic sugar.

@braveg1rl

Is yield truly sugar? How about call stacks and such. If it's sugar, then I expect the call stack to look really weird, compared to what you see in code. Because anything that works a bit like "yielding" in ES5 involves a function that returns immediately, and calls a callback later. Hence very weird call stack. The call stack coming from the originator of an event. I absolutely haven't studied this.

@juandopazo
Contributor

@erights I thought there was no consensus for await since it prevented JS from being run-to-completion.

@braveg1rl

What I mean to say is, when you write "yield", are the statements coming after that put in a "virtual", invisible function that's being called later on with the right function context and arguments?

Ok, maybe it's so. Are ES6 interpreters gonna translate the errors (and call stacks) coming from that, hiding the fact that there were such "implicit" functions?

@juandopazo
Contributor

@meryn no, yield is not await. yield works in the context of generators. You can read about them at MDN. Taskjs uses generators to chain promises under the hood. There are no magic transformations there.

@braveg1rl

I know (or rather, indeed believe) that task.js doesn't do magic transformations. But how many people are going to agree that generators and yield are "syntactic sugar" and not a new language feature? And if I misunderstand the definition of sugar (I'm not an expert on the phenomenom of computer languages...), let's be concrete:

What happens if something coming after the yield - yet inside the same generator - throws an error? Could the caller of the generator catch it, or not?

I mean, maybe interally, somewhere deep down it's all an illusion, but if generators+yield introduce more sync-like possibilities (i.e. catching errors) into the async world, then it'd be a mighty fine illusion.

This is what I call sugar:
translating

do
  text <- readFile "foo"
  writeFile "bar" text

into

readFile "foo" >>= writeFile "bar"

That is, I suppose that >>= writeFile "bar" comes down to a callback of some sort.
Hence, my question - yet unanswered? - about what happens to the errors there.

@braveg1rl

I'm probably misunderstand the whole thing about generators. Relevant quote from MDN

When a generator function is called the body of the function does not execute straight away; instead, it returns a generator-iterator object. Each call to the generator-iterator's next() method will execute the body of the function up to the next yield expression and return its result. When either the end of the function or a return statement is reached, a StopIteration exception is thrown.

I ought to study it more. Or maybe it's just more of weak-recollection of what exactly yield was doing, and what parts were taken up by task.js.

Reading it like this, I expect the calll stack actually not to become that much friendlier, or any friendlier at all. It's probably just task.js which is gonna call this "generator-iterator" object for the second time in response to having "received" a resolved promise (the one returned by the earlier yield). So originating call will be the event that caused the promise to resolve, and responsibility for dealing with errors thrown lies with task.js, which can catch it and fail the promise it has generated earlier.

Still, I'd love to know how Haskell deals with errors in the aforementioned example.

@puffnfresh

@meryn readFile "foo" >>= writeFile "bar" is readFile "foo" >>= (\contents -> writeFile "bar" contents) - so yeah, callbacks.

The >>= operation focuses on the "main" case. Errors must be explicitly handled "outside" of >>=. For example, a global error handler:

handleErrors errorHandler $ readFile "foo" >>= writeFile "bar"

Or a specific error handler:

readFile "foo" >>= (\contents -> handleErrors errorHandler (writeFile "bar" contents))

(better in do-notation)

do
  contents <- readFile "foo"
  handleErrors errorHandler $ writeFile "bar" contents

Does that make sense?

@braveg1rl

So assigning a global error handler is probably a bit like node.js domains. (although I haven't studied that properly either).

I think I'd rather have a language that supports wrapping try catch around all such statements. Come to think of it, maybe a function could send along its own errorhandler function with each call it makes. Of course it can, that's just the anti-node double-callback style. That "default" of keep sending along the same errorhandler function could be given sugar, and then you can write

try
  thismakesACallThatResultsInEventualFailure arg1, arg2, ->
   someMore (result) ->
     yetMore (result2) ->
       console.log result2 # only reaches this point when successful
catch (error) ->
  console.error error

Note the "function" in catch. The error handler is send as last argument to the function. The important thing here is that as soon as you do this, then later propagation of the error callback becomes implicit.

But then, this is not really important for as long I'm not going to design my own language... Or it's something that might be suitable for CoffeeScript. CoffeeScript would affix error callback to the end of the arguments of all functions inside the try block. This would require adapting node-style functions to double-callback style first of course.

@juandopazo
Contributor

@meryn if I understood correctly (I haven't read its source yet), this is what Taskjs does:

function foo() {
  var text = yield when.resolve('hello world');
  console.log(text);
}

var gen = foo();
var promise = gen.next();
promise.then(function (value) {
  gen.send(value);
  gen.next();
});
@puffnfresh

@meryn that's a side-effectful way to do the above. I'd rather do it with values. Always use values instead of not-values, when possible.

@braveg1rl

@pufuwozu "side-effectful", that's latin greek to me. ;) What are the side effects here, I wonder?

FP programs may have "effects" right? :) That is, there used to be a point where I thought that all they were allowed to do was compute one value, and then quit. But as I've learned, they can do plenty of io in the mean-time. Otherwise they wouldn't be that useful.

@puffnfresh

@meryn functional programming represents effects as values (see the IO monad). "Not functional programming" doesn't represent effects as values, which is where "side-effects" come in. Code is being evaluated (i.e. you're trying to get a value) but something observably happens in the mean time.

Functional programming does nothing but build up a value which represents which effects to execute. The runtime then executes those effects, based on the value. Allows you to reason about programs as just a composition of values to be evaluated.

The side-effect with try/catch is that a value is trying to be evaluated but then the program jumps to the catch block. The evaluation of a function is changed.

@erights
erights commented Apr 15, 2013

@juandopazo As Q and task.js showed, "await" can be easily expressed as a pattern using generators/"yield" and promises. The reason neither "yield" nor "await" violate run-to-completion is that JS generators are shallow. This related to the syntactic sugar issue explained above. Generators/"yield" can be compiled away by a local cps (continuation passing style) transform of the generator function taken by itself. It does not threaten the run-to-completion semantics of anything else in the program.

@juandopazo
Contributor

@erights I see. Thanks. Does that mean that in order to introduce await in the language you'd also have to introduce promises?

@braveg1rl

functional programming represents effects as values

@pufuwozu
So would I - in effect - have to "return" the console.log result "expression" to the original caller? If this were a sync program, I'd consider that a bit complicated, but at least could understand how. But in async, there's not even a caller to return to. Or would I be returning to the function that originated the call stack? That doesn't make sense to me.

Maybe you know about a thoroughly explained example of some real-life io (a bit more complicated than hello world, but not much) in both sync and async fashions? I say thoroughly explained for a reason, because I'm just not getting you. It's like your speaking a whole other language, really. Quite amazing to me.

I also still don't understand what a monad is. Although I do know now (because of your post) that it's in some way similar to a promise, which could be a handy point of reference. I haven't known about promises that long.

@braveg1rl

I do wonder, if the point is to indeed return a kind of "request for io" to the runtime, if it'd be possible to make a kind of FP adapter for say node.js. Let all outside events be handled by a handler function which also happens to know IO, so it can do this IO as soon as some event results in a function that demands IO. (by returning an IO "nomad" object)??.

@braveg1rl

Maybe actually an FP i/o example in javascript/coffeescript (for node.js) would be best. Then at least I wouldn't have the trouble following the semantics of the language.
What would happen after

handleRequest = (req, res) ->
  ## create a FP adapter here?
@ForbesLindesay
Member

If you want to introduce await you have to introduce promises (or something that behaves like promises).

To answer someone's earlier point about throwing errors in a function that yields:

Consider the following function with a yield in it.

var getJSON = Q.async(function* (url) {
  validateURL(url);
  var response = yield get(url);
  if (response.statusCode != 200) throw new Error('status code ' + res.statusCode);
  return JSON.parse(response.body);
});
var promise = getJSON('http://example.com/foo.json');

The above code never results in an unhandled exception, under any circumstances. Assuming get is a function that takes a url and returns a promise for the response from the server, promise will be a promise that is either going to be rejected or fulfilled at some time in the future.

There would be two ways of consuming this API:

var magic = Q.async(function * () {
  try {
    var obj = yield getJSON('http://example.com/foo.json');
    //..do some magic with obj...
    return obj;
  } catch (ex) {
    // handle error
    // this would be where you'd handle server errors
    // that resulted in getJSON returning a rejected promise
    throw ex;
  }
});
// or
var magic = getJSON('http://example.com/foo.json')
  .then(function (obj) {
    var obj = yield getJSON('http://example.com/foo.json');
    //..do some magic with obj...
    return obj;
  })
  .then(null, function (ex) {
    // handle error
    // this would be where you'd handle server errors
    // that resulted in getJSON returning a rejected promise
    throw ex;
  });
@braveg1rl

Google is not being helpful here. As far as I understand it now, I must return IO nomad to a runtime. I suppose such a thing could be simulated in Javascript. Yet, googling for "functional programming node.js " doesn't give me anything about IO, I'd say about the biggest hurdle to take. Instead, you get links to things like underscore library, which just provides some functions. Hey, I know functions.

This proves somewhat helpful: https://www.fpcomplete.com/school/basics-of-haskell/3-pure-functions-laziness-i-o-and-monads

Does anyone here know about I/O monads implemented in javascript? I assume they would need some kind of "runtime" object/function too, to which these monads can be returned to in order to be evaluated and turned into actual I/O.

Secondly, are there monads which allow me to store state somewhere?
Suppose I issue two SQL SELECT queries. I'll get them back via two different callbacks. I don't think that when the second callback gets called, I can still access the value of the previous. That would mean some state there. Thus I probably would need to get this first result out of the "program" somehow first. If I could only return I/O monads I wouldn't be able to combine them meaningfully (i.e. sum two numbers or so). Or maybe I have to pass on a partial built result down the chain of async calls I'm making?

@erights
erights commented Apr 15, 2013

@juandopazo writes "Does that mean that in order to introduce await in the language you'd also have to introduce promises?" IIUC, yes by definition. That's what await does.

@paulmillr

@meryn I strongly suggest you to learn functional programming. The direct benefit of that is that your code will be much more simple in debugging.

Since I started writing in pure-functional style (at least, trying), my total bug count become like 50-80% less.

I can suggest several simple tactics:

  1. Assign your variables only once when this is possible. That’s it. If you do user = getCurrentUsers(), don’t do user = user.toLowerCase().replace(' ', '-');. Instead, assing stuff to new variable like stripped = user.toLowerCase().... This is one less side effect (mutation). Also, don’t use loops, create new structures instead of mutating them in-place.

  2. Prefer super-simple functions that do only one thing to mutable object instances and stuff. Make them take one input, return one output. Here’s an example of FP-style code I write every day. It gets all comments on current page and outputs messages.

    First style: anonymous functions, which capture some outer variables (I prefer).

    var stats = [].slice.call(document.querySelectorAll('.comment-header-author'))
      .map(function(_) {return _.innerText})
      .reduce(function(counts, _) {
        if (!counts[_]) counts[_] = 0; counts[_] += 1; return counts
      }, {});
    
    Object.keys(stats)
      .sort(function(a, b) {return stats[b] - stats[a]});
      .map(function(name) {
        var count = stats[name];
        var text = count === 1 ? ' comment' : ' comments';
        return 'User ' + name + ' had left ' + count  + text;
       })
      .forEach(function(_) {console.log(_)});

    Second style: named pure functions that don’t capture any outside variables:

    // This particular function is not pure (side-effect free), as it works with DOM.
    var query = function(selector) {
     return [].slice.call(document.querySelectorAll(selector));
    };
    
    var attr = function(name) {
     return function(item) {return item[name]}
    };
    
    var sum = function(counts, item) {
     if (!counts[item]) counts[item] = 0;
     counts[item] += 1;
     return counts;
    };
    
    var byValue = function(values) {
     return function(a, b) {
       return stats[b] - stats[a]
     };
    };
    
    var format = function(stats) {
     return function(name) {
       var count = stats[name];
       var text = count === 1 ? ' comment' : ' comments';
       return 'User ' + name + ' had left ' + count  + text;
     };
    };
    
    var log = function(message) {
     return console.log(message);
    };
    
    var stats = query('.comment-header-author').map(attr('innerText')).reduce(sum, {});
    Object.keys(stats).sort(byValue(stats)).map(format(stats)).forEach(log);

    This is like super easy to debug.

  3. Maybe take a look at LiveScript, functional to-js lang.

Basically monads, functors, monoids and stuff are functional ways to do the job, they give the same benefits as FP — better code reusability, easier debugging etc. Think of them as OOP interfaces. Brian and folks are trying to define interfaces for languages in order for libraries to have better compatibility between each other.

@braveg1rl

@paulmillr I'd generally go for first style too. For sync stuff, I'm always using CoffeeScript list comprehensions though. I don't think writing out the name of an intermediary result (authorNames) for example hurts anyone. Such a variable gets destroyed really soon. Nice to be able to put console.log in front of it at some point too.

I don't think your code examples captures the essence of FP though. It's just a matter of writing non-surprising, maintainable code.
I mean, where are the "monads" here? They're supposed to implement both "bind" and "unit". Wise-man, me. ;)

It's code like the following that makes my head spin:

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);
        };
    }));
}

I'm in the process of decoding it. First step was to clean things up a bit. ;)

# when promise resolves, apply fn onto it, but keep promise interface
# perhaps "map" functions in general are defined by the fact that they
# take an object and a function, and return an object of the same type
map = (promise, fn) -> 
  promise.then (a) -> 
    p.constructor.point fn a
   # why not simply do mySuperModule.makeResolvedPromise fn a
   # yup, it's global, but removes need for "point" to exist on constructor


ap = (valuePromise, functionPromise) ->
  functionPromise.then (fn) -> 
    map valuePromise, fn

# ifting is a concept which allows you to transform a function into a 
# corresponding function within another (usually more general) setting
# liftA2 probably is a specific lift. 
# It's "lifting" two promises, while A'ing it?
liftA2 = (promiseA, promiseB, fn) ->
  ap promiseB, map promiseA, (a) -> 
    (b) -> fn a, b

@pufuwozu Care to think up a descriptive function name for "ap" and explaining in prose what it does?
Also, why do you think you need this point function set on the constructor while you could just as well use an external module for that? Just because you want to keep the map function pure?

@braveg1rl

constructor.of creates a monadic value of the same constructor

This comment here is interesting because you say "same constructor", as though you expect the constructor to differ, to matter. Normally, a promise would just be a promise. Just use a proper library to construct it.
Are you referring here to "typed" promises? Say ArrayPromise or NumberPromise? If so, why do you want a type? It doesn't make sense to want a type (in Javascript) unless it actually gives functionality. Do you want to put extra (type-specific) methods on the prototypes? If so, then we're talking about way bigger prototypes anyway (compared to the tiny surface of a promise/a+ promise). An extra method (say, this "of") matters jack in that context. But it's a whole different spec. If a function receives a promise which doesn't have an "of", you just assume it to be typeless, and just a global (generic) resolvedPromiseConstructor.

@braveg1rl

I'll implement "of" for you on my "promised builtins". Two people who agree makes a standard, right? ;)

@puffnfresh

@meryn this is abstract algebra. ap is as descriptive as it gets.

f.ap(b)

f is an Applicative of a function. b is an Applicative of an argument to the function in f. The ap method applies the contents of b to the function inside of f. All Monads are Applicatives. Easy.

Your readable version assumes all input arguments are promises. Again, this is abstract algebra - the inputs do not have to be promises.

The of function needs to go on the value or the constructor - again, it's abstract algebra - there's many structures that need to implement that function. We have no idea what module the function could be coming from. If we made a module with all "known" applicatives/monads then we're assuming the world is closed and nobody could ever make their own. The interface is documented in the Fantasy Land specification.

Ignore the "same constructor" stuff and read the Fantasy Land specification.

I'd have a hard time to call Fantasy Land a standard but it's definitely a specification of common algebraic structures. There's already quite a few libraries that implement it.

@braveg1rl

@pufuwozu With regard to your two proposals:

  1. of or point on constructor
  2. removing onRejected callback from then, and instead have a dedicated onRejected function

Which of the two is most important? I'd say 1: It gives possibility to do fancy stuff with "typed" promises.
How much does 2 add, except for in your perception a nicer interface? Is there anything wrt "composability" that gets totally broken because of the two arguments to then?

@puffnfresh

@meryn of is definitely the most important thing. Feel free to ignore onRejected - I just dislike the overloaded then but that's not going to change. I can just not use it and then nothing breaks.

Thanks.

@braveg1rl

Lol, I stumbled upon that fantasy land spec today, skimmed over it but immediately ignored it just because of the WAY OUT THERE terminology. I'm like man, what have you been smoking. :) Monoids, functors, never heard of them.

Please don't take this wrong.
I don't think it's entirely fair to ask someone to read a spec with words totally new to them. Certainly if it's just to make the case for a simple "of" method on a promise constructor. You ought to be able to make the case in plain English.

And yes, I may eventually take the time to study your spec, as part of my ambition to get to know FP a little better.

@braveg1rl

Maybe other people here all get it. Maybe I'm just the uneducated one. :)

@puffnfresh

@meryn it's abstract algebra. Should we rename monad to "warm fuzzy thing"? Who is that going to help?

I think it's completely fair to ask someone to read a spec that contains words they don't understand. The definitions are provided.

Please don't ignore things you don't understand.

@ForbesLindesay
Member

No, you're not just the uneducated one. I have explored the concepts before a little bit and they could be explained just as easily using more commonly understood words. The only reason they're using the words they are is that those are the words that have been allocated by mathematicians who are used to the jargon.

It concerns me slightly that people reading these posts are starting their exploration of FP with monads. Functional Programming has a lot to offer and very little of it involves monads. Monads started life as the rather awkward way of expressing state and IO in a pure functional language. They turned out to be a very elegant way to represent asynchrony, hence our adaption of them to form promises.

The starting principal of functional programming is to have no mutable state. What that means, is you can't have the assignment operator in JavaScript. This would also make while loops and for loops completey unusable since you could never escape them. You can still have variable declarations (e.g. var x = 10;) just not assignment operations (e.g. x = 20;). That way, once declared, a variable never changes.

It turns out that this doesn't really limit your expressiveness, other than making it difficult to build user interfaces and IO systems. In general, where possible it's worth writing most code in a purely functional style except where:

  1. You need to do IO
  2. You need to build a UI (User interfaces are essentially just another sort of IO)
  3. You have an algorithm that really lends itself to a loop and would be awkward to implement any other way.
@ForbesLindesay
Member

By allowing monads to exist as a first class citizen of the language, functional programming can work with IO. This makes it possible to build anything in a purely functional world. Personally I prefer to write UIs and web servers using the imperative parts of JavaScript, use for loops when an algorithm makes that seem much simpler than recursive functions, and aim to use a functional style when it's sensible to do so.

@braveg1rl

Ok, let me give an example of making the case for "of"

Let's add an of method "to" the constructor of every promise, so we can generate a new promise of the exact same type as the promise in question. Why? Because then we could have multiple types of promises, and if we could have multiple types of promises while being able to keep their types consistent when you "derive" a new one from it, I could build really, really, really cool stuff with, involving fancy maths and computer sciences. But even if you don't understand the maths or sciences, you could benefit from my work.

I think that's a pretty strong case while not introducing much terminology considering people who know English, Javascript and promises, something you can very rightly assume of your audience.

@puffnfresh

@ForbesLindesay pure IO is not clunky, functional user interfaces are expressive and give those algorithms a pure interface then I'll be satisfied.

@puffnfresh

@meryn stop being silly. The specification is not hard just because there's a couple of words you don't understand.

@Twisol
Twisol commented Apr 15, 2013

@meryn: In the privacy of my brain, I prefer to call it construct instead of of. It aligns more easily with known idioms, especially in JavaScript, because you're just taking a value and constructing a new context around it.

ap is function application when both the function and its arguments are wrapped in these objects. (function(x) { return x + 1; })(a, b, c) is ap in the Identity object; but in List you can have:

[ function f1(x) { return x + 1; },
  function f2(x) { return x - 1; }
].ap([1, 2, 3], [2, 4, 6])

In other words, applying a list of functions over a pair of lists of parameters. The result is:

[ f1(1, 2), f1(1, 4), f1(1, 6), f1(2, 2), ..., f1(3, 6),
  f2(1, 2), f2(1, 4), f2(1, 6), f2(2, 2), ..., f2(3, 6)
]

But the point is that you've generalized function application within any Applicative context. A Promise-based ap would allow you to return a function as the result of a promise, and then apply other values that are also Promises to that function. (In other words, the same as normal application, but with the benefit of asynchronous composition.)

@braveg1rl

@pufuwozu Allright I stop being silly. However, I think you do overestimate the patience of an incredible amount of people you might want to pitch your idea too. Ever heard of the term "elevator pitch" (wrt to pitching to potential investors)? Did you know people write endless amounts of blog posts about the art of writing a single headline for a product?

@robotlolita

You need to do IO

I don't see how you can't do that in a pure language. The only difference is that you have effects instead of side-effects, which have the benefits of::: making your code easier to reason about.

You need to build a UI (User interfaces are essentially just another sort of IO)

Take a look at Functional Reactive Programming. There's even one implementation that compiles to JavaScript called Elm.

You have an algorithm that really lends itself to a loop and would be awkward to implement any other way.

Laziness and recursion.

@braveg1rl

People so easily tune out when things get a bit too hard. I'm practically 100% self-taught, I know about doing work, and persistence. But some things (if only at first glance) just don't seem to have the right effort/reward ratio.

@ForbesLindesay
Member

@pufuwozu You totally miss the point. I can interpret your spec, but a lot of time and thought and effort has gone into making this spec easy to understand and read. It's still not perfect, but right now it should be high on our list of priorities to make it easier to interpret. It's us who want other people to take up using promises, not other people who want to start using them. There are also many more people reading your spec than there are writing it.

@puffnfresh

@meryn I hope most developers can read words they don't understand and then read the definitions of them. Hopefully peer pressure and a really cool logo will entice them to join our fantasy land 😉

@ForbesLindesay
Member

@pufuwozu it is aptly named at the moment

@puffnfresh

@ForbesLindesay I demand a proof that you can rewrite the specification using plain English. I'm waiting.

@robotlolita

Protip: there are times where plain English will make things more confusing. Specifications are one of those things where you want more formalism and less room for ambiguity. Otherwise, how can you be sure implementers will be able to read, understand correctly what you mean, and create a faithful implementation (or derive things)?

@braveg1rl

Well yes but I already have my "promise land", which I think is cooler, and actually pays homage to the fantastic work the Promise community has been doing. Right now, I'm not sure I'd really care much for the neat FP constructs. But then, maybe I will. My motivation is different. I want to make dealing with promises feel as much as the same as dealing with the objects they "proxy". For all else, I see a more or less "imperative" world before me, just like I always have.

In a way, there are big similarities to promise land and fantasy land though. They're both kind of "dreamy" targets, idealistic, and they both want to take promises (or maybe in your case: monads) to a new level. In Javascript. And resulting code might as well be able to interoperate.

@Twisol
Twisol commented Apr 15, 2013

@ForbesLindesay:

The only reason they're using the words they are is that those are the words that have been allocated by mathematicians who are used to the jargon.

I agree 100% with this statement. Polymorphism, interfaces, and SOLID principles are just as impenetrable to newcomers, but there is a vast amount of pedagogical material and experience built up around them. There is no reason why we can't make these concepts more approachable.

The starting principal of functional programming is to have no mutable state.

Not true. There are many functional languages that support mutable state (and even globals). You can still exploit abstract idioms to structure your code - and JavaScript is known to be inspired by a number of functional tenets. First-class functions as values, for example.

@killdream:

Protip: there are times where plain English will make things more confusing

True. Of course, there's no reason we can't motivate things in plain English, then specify it more precisely.

@robotlolita

@Twisol yes. You can totally have an informal introduction describing the thing in a higher level, then have a section where you formally define things. R7RS does this.

@ForbesLindesay
Member

@killdream I'm not saying you can't do those things functionally. I'm just saying they're better used in other ways.

@puffnfresh

@ForbesLindesay still awaiting your proof. Thanks.

@robotlolita

@ForbesLindesay I disagree. I have yet to find a better way to write UIs than Functional Reactive Programming. Ditto for the other stuff. Though I'm not a strong proponent of enforced purity everywhere (I'm mainly a Clojure/Lisp programmer)

@braveg1rl

Have you guys ever heard of the curse of knowledge? http://en.wikipedia.org/wiki/Curse_of_knowledge

The curse of knowledge is a cognitive bias according to which better-informed people find it extremely difficult to think about problems from the perspective of lesser-informed people.

That's imo what's going on here. I think the FP guys have no idea how much ahead they are of others in some regards.

@ForbesLindesay
Member

@killdream I did not say that all functional languages have no immutable state. I said it was the starting point of functional programming. Strictly speaking pure functional programs do not have mutable state. There are very few (popular) pure functional languages.

Languages that are functional but also provide mutable state are great. I'd say they're ideal. It's part of the reason I love coding in JavaScript. They aren't strictly speaking pure though.

@puffnfresh

I actually agree with @ForbesLindesay. Functional programs work on values which implies immutable state. The most practical way to program.

@ForbesLindesay
Member

@pufuwozu my version of your spec with jargon reduced: https://gist.github.com/ForbesLindesay/5392337

N.B. this is a first draft as a proof of concept that it's not necessary to add the jargon. It took me a little while to see but there was a light-bulb eureka moment when I realized that you weren't really specifying things (monad, semigroup) which were really difficult to give nice intuitive names, you were specifying a short series of functions, which were really easy to name.

I would probably add a footnote to each that says, "An object that implements concat would be called a (Semigroup)(link to more info on semi groups here) in category theory" and so on for all the other rules.

@braveg1rl

@ForbesLindesay Are these specs describing the same thing??????

I can't explain how much difference it makes. It's like day and night, black and white.

@ForbesLindesay
Member

Yes, I believe so. Like you I found the previous spec hard to interpret, so I may have missed something in the detail, but I think they are broadly equivallent.

@braveg1rl

Ok without reading it, I think it could a (one-paragraph) "statement of purpose" on top. Why make this spec in the first place. That's unclear to me.

i.e. "Having this spec (and/or having code conforming to this spec) will bring us/you/me ..."

@ForbesLindesay
Member

For of, I'm particularly uncertain of my wording:

  1. If f is a function that returns an entity of class A, then A.of(b).chain(f) is equivalent to f(b)
  2. A.chain(A.constructor.of) is equivalent to A
@ForbesLindesay
Member

I didn't think it would be possible to make the following hold for all f where a represents A.of(b) returns a promise for b:

A.of(b).chain(f) is equivalent to f(b)

@ForbesLindesay
Member

I included that in the first paragraph of "Real World Specification":

The motivation behind this is to encourage greater code reuse.

@braveg1rl

I think it's an incredible feat. In particular because you kind of demistify something complex to me.
Again I stress that I have yet to read this properly, but right now I know I'm going to understand this. And at the same time, I find it hard to believe that the old spec said about the same thing. Disbelief.

I scanned over it lol.

Is anyone here familiar that people typically don't "read" on the web? They scan. Jakob Nielsen (Alertbox) has written prominently about it.

Ok I'll stop critiziing what I haven't read and start reading it.
But I'm incredibly impressed. "Total makeover"

@ForbesLindesay
Member

Also, as I understand it, the following would be a valid implementation of chain for all promise libraries (since the spec leaves behavior with incorrect types undefined):

Promise.prototype.chain = function (fn) {
  return this.then(fn);
}
@braveg1rl

The spec defines some very general "interfaces" which are not only defined by their method signatures, but also by some "relationships" which must hold. So then you can reason about all these different kind of objects in the same manner. Code may know how to fancy optimization tricks.

I have an example which may not be covered by the spec, but I think it's nice.
Let's define an object "obj" that has a method "transform". Now when you call obj.transform with value a, and you get in return value b, than you may be certain that any other time you call obj.transform with value a, you can be sure that the return value will be b again. Also, there will be no side effects. Or if there are, the method is "idempotent" (that's a term from REST, Roy Fielding). So if you have called the method once, you can call it any time extra and it doesn't matter.

Hence, return value doesn't have to be recomputed when input stays same. It can be cached. Memoization can be "externalized".

I can imagine many other fancy tricks are possible with strong specs. Perhaps not all performance related.

But this is cool. I'm in favor. I'm sold. Where can I get it? ;)

Oh yeah, I know REST and the concept of the uniform interface constraint.

@braveg1rl

I think this is great, and if realized, may have great impact. It's hard to judge in advance though. What I do know, is that I'm charmed by the idea. In a way, the objects are the resources from REST. The "consuming" libraries are somewhat like web browsers. Connected via a limited amount of verbs, with strong restrictions imposed.

@ForbesLindesay
Member

This and some parallel discussions have all ultimately resulted in #101, which I hope will lead to something that makes everyone happy.

@medikoo
medikoo commented Apr 16, 2013

@ForbesLindesay Your poc version, indeed enlightens a lot, great work! Thanks for that. We definitely need such bridge to JS land.

@AriaFallah

Well that was informative 😄 thanks @puffnfresh

@staltz staltz referenced this issue in tc39/proposal-observable May 31, 2016
Merged

Remove forEach and add "subscribe" overload #97

@donhatch

In @domenic 's April 11 2013 comment, ey said "it has previously been discussed over and over that a promise-for-a-thenable is a problematic pattern in JavaScript and should not be supported".

Can someone provide some links to such discussions?
At this moment I have no idea why anyone thinks special-casing thenables is a good thing.

@rpominov rpominov referenced this issue in fantasyland/fantasy-land Nov 22, 2016
Open

Fantasy Land proposal process for ECMAScript #204

@donhatch
donhatch commented Feb 7, 2017

In answer to my Oct 21 question, I found these:

and some great discussion threads linked from comments on the latter (in particular, from the two Apr 25, 2013 comments).

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