Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Incorporate monads and category theory #94

Closed
paulmillr opened this issue Apr 10, 2013 · 234 comments
Closed

Incorporate monads and category theory #94

paulmillr opened this issue Apr 10, 2013 · 234 comments

Comments

@paulmillr
Copy link

@paulmillr paulmillr commented Apr 10, 2013

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
Copy link

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

@domenic 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
Copy link
Member

@briancavalier briancavalier commented Apr 10, 2013

@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
Copy link

@puffnfresh puffnfresh commented Apr 10, 2013

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

@juandopazo juandopazo commented Apr 10, 2013

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
Copy link

@puffnfresh puffnfresh commented Apr 10, 2013

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

@ForbesLindesay ForbesLindesay commented Apr 10, 2013

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
Copy link
Member

@ForbesLindesay ForbesLindesay commented Apr 10, 2013

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
Copy link
Member

@briancavalier briancavalier commented Apr 10, 2013

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
Copy link
Contributor

@juandopazo juandopazo commented Apr 10, 2013

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
Copy link
Member

@ForbesLindesay ForbesLindesay commented Apr 10, 2013

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
Copy link

@puffnfresh puffnfresh commented Apr 10, 2013

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

@ForbesLindesay ForbesLindesay commented Apr 10, 2013

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
Copy link
Contributor

@juandopazo juandopazo commented Apr 10, 2013

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
Copy link

@puffnfresh puffnfresh commented Apr 10, 2013

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
Copy link

@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
Copy link

@puffnfresh puffnfresh commented Apr 10, 2013

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

@juandopazo juandopazo commented Apr 10, 2013

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

@ForbesLindesay
Copy link
Member

@ForbesLindesay ForbesLindesay commented Apr 10, 2013

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
Copy link

@kennknowles kennknowles commented Apr 10, 2013

+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
Copy link

@puffnfresh puffnfresh commented Apr 10, 2013

@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
Copy link

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

@ForbesLindesay ForbesLindesay commented Apr 10, 2013

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
Copy link

@puffnfresh puffnfresh commented Apr 10, 2013

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

@domenic
Copy link
Member

@domenic 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
Copy link
Member

@ForbesLindesay ForbesLindesay commented Apr 10, 2013

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

@Raynos
Copy link

@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
Copy link

@puffnfresh puffnfresh commented Apr 10, 2013

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

@ForbesLindesay ForbesLindesay commented Apr 10, 2013

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

sderosiaux added a commit to sderosiaux/every-single-day-i-tldr that referenced this issue Apr 14, 2018
@dmitriz
Copy link

@dmitriz dmitriz commented Apr 21, 2018

This example might be relevant to illustrate the danger of the current Promise implementation mixing .map and .chain behaviors into .then:
https://codepen.io/dmitriz/pen/rvNWxw?editors=0012

let val = {
	then: x => x
}
let val1 = {
	then: x => x(1)
}
let val2 = {
	then: {x:1}
}

// Nothing is getting out of this promise
// with  the `.resolve` not actually resolving
Promise.resolve(val).then(result => {
	console.log("I am silently ignored!")
	console.log("Result is:", result);
})
.catch(e => {
	console.log("I am also silently ignored!")
	console.log(e)
})

Promise.resolve(val1).then(result => {
	console.log("Result1 is:", result);
})

Promise.resolve(val2).then(result => {
	console.log("Result2 is:", result);
})

It can be argued that the case in the first example is rare, but if it might occur unexpectedly, the code becomes hard to debug and generally unsafe.

Is there any reason not to provide the same Promise class with the safer .map and .chain methods in addition to the (looking more convenient but) potentially dangerous .then?
Or is any library doing this already?

@robotlolita
Copy link

@robotlolita robotlolita commented Apr 21, 2018

@dmitriz we've discussed it extensively when ES6 was being discussed and the conclusion was: you can't have both (https://esdiscuss.org/topic/promise-cast-and-promise-resolve#content-40). They chose the non-monadic version of promises, and even though I still think that was a bad choice it's what we've got now.

You can just not use promises and write your own types, then build something like async/await on top of generators, and you'll get roughly the same characteristics Promises give you by default, but without the drawbacks (but also without the runtime optimisations).

@bergus
Copy link

@bergus bergus commented Apr 21, 2018

You can just not use promises and write your own types, then build something like async/await on top of generators

…or use an existing library that does just that, like Creed :-)

@dmitriz
Copy link

@dmitriz dmitriz commented Apr 22, 2018

@robotlolita
Thank you very much for the link, an interesting read, when also frustrating for an outsider as everything refers to some context that is often to be found elsewhere or spread over multiple places. It is unfortunate that people now erroneously say Promises are monads, where people looking for explanation seem to have hard time finding it concisely in one place.

I must admit my PhD degree in Maths still does not help me to extract from that @BrendanEich comment nor the following comments debating these, a scientific rigorous proof that implementing .then on top of the more primitive .map and .flatMap (aka .chain) and exposing all of them on the Promise instance is fundamentally impossible.

Is there a such proof written anywhere that is complete, objective and not opinion based?

That is, a proof that is impossible to have a Promise class having exactly the same methods as currently implemented, plus the .map and .chain. (As a matter of personal opinion, I find .flatMap more descriptive than .chain that has already been used in JS to mean other things.)

You can just not use promises and write your own types, then build something like async/await on top of generators, and you'll get roughly the same characteristics Promises give you by default, but without the drawbacks (but also without the runtime optimisations).

Could you please explain the runtime optimisation? Wouldn't the native .chain be more performant (and .map even more), comparing to .then with all its plumbings?

@dmitriz
Copy link

@dmitriz dmitriz commented Apr 22, 2018

@bergus

…or use an existing library that does just that, like Creed :-)

Thank you, this is what makes writing my comments worth is ;)

So if this library can implement all the methods simultaneously without breaking .then, doesn't it refute the arguments in https://esdiscuss.org/topic/promise-cast-and-promise-resolve ?

@bergus
Copy link

@bergus bergus commented Apr 22, 2018

@dmitriz

but also without the runtime optimisations

Actually that's wrong. Algebraic promises can be much better optimised than promises which need to do recursive flattening in every step.

Doesn't it refute the arguments in https://esdiscuss.org/topic/promise-cast-and-promise-resolve?

That's a very long and very old thread, even using names that had changed their meaning since. Which argument in particular do you refer to?

Creed does not break then, but it does break interoperability. You only get Promise/A+ assimilation interoperability by using the "broken" then method, which cannot be fixed and maintain compatibility (at least about recursive assimilation). You can add the more primitive operations like chain and of and map to an A+ compatible library, but they will only work with promises from that particular library.

@robotlolita
Copy link

@robotlolita robotlolita commented Apr 22, 2018

@dmitriz

a scientific rigorous proof that implementing .then on top of the more primitive .map and .flatMap (aka .chain) and exposing all of them on the Promise instance is fundamentally impossible.

It's possible to write that, and easy enough. The problem, really, is that it creates more room for confusing behaviour. People using .then expect promises to always be flattened, people using .chain expect promises to never be flattened. When you start using other people libraries, and those expectations combine in the code, you're most likely going to run into bugs that are hard to track because people had different expectations about how to go about writing the code.

The whole point of algebraic structures is to define a single way of composing structures such that you can always reason about their composition in a simpler way. If you start adding too many ways of composing values, that quickly falls apart, and you're left with none of the benefits.

@dmitriz
Copy link

@dmitriz dmitriz commented Apr 23, 2018

@bergus

but also without the runtime optimisations

Actually that's wrong. Algebraic promises can be much better optimised than promises which need to do recursive flattening in every step.

That was my thought too. Perhaps @robotlolita meant the unwrapping optimisation if you re-implement it on top of .chain. But you can also use the native .then if you have to.

That's a very long and very old thread, even using names that had changed their meaning since. Which argument in particular do you refer to?

The argument that it is "fundamentally impossible to have both functionalities coexisting", e.g. here:
https://esdiscuss.org/topic/promise-cast-and-promise-resolve#content-40

Creed does not break then, but it does break interoperability. You only get Promise/A+ assimilation interoperability by using the "broken" then method, which cannot be fixed and maintain compatibility (at least about recursive assimilation). You can add the more primitive operations like chain and of and map to an A+ compatible library, but they will only work with promises from that particular library.

The monadic .chain (or .flatMap) should expect function returning the same monad. Do you mean, passing the native promise inside the .chain is not recognized by creed?

The .map and .of are simpler in that they merely wrap the value. So in what way should they work with other libraries?

@robotlolita

It's possible to write that, and easy enough. The problem, really, is that it creates more room for confusing behaviour. People using .then expect promises to always be flattened, people using .chain expect promises to never be flattened. When you start using other people libraries, and those expectations combine in the code, you're most likely going to run into bugs that are hard to track because people had different expectations about how to go about writing the code.

I don't quite understand how it is different from using any library X with two different methods a and b. Some folks will use a and get functionality of a, others will use b. Any compliant promise would have to provide both. The methods are called and work differently, yes. Confusion is of course always subjective and hard to debate for that reason, but is there any fundamental reason it would be impossible for the two methods to coexist?

The whole point of algebraic structures is to define a single way of composing structures such that you can always reason about their composition in a simpler way. If you start adding too many ways of composing values, that quickly falls apart, and you're left with none of the benefits.

An algebraic structure is just a function with several arguments, satisfying certain properties. Even integers have two separate algebraic structures -- + and * that perfectly coexist with no confusion.

Why there should be a single way to define it? I don't mean any personal preference here but a fundamentally possible way to satisfy different people's needs.

@dmitriz
Copy link

@dmitriz dmitriz commented Apr 23, 2018

@bergus
I do not know if that addresses the interoperability concern,
but it seems creed.chain works as desired even with native promises:

> creed = require('creed')
> p = creed.resolve(1).chain(_ => Promise.resolve(2))
Promise { pending }
> p
Promise { fulfilled: 2 }
@bergus
Copy link

@bergus bergus commented Apr 23, 2018

@dmitriz, I mean something like

import creed from 'creed';
import other from 'some-other-algebraic-promise-lib';

const p = creed.resolve(other.of(other.of(1))); // similar, in creed.of(…).chain(_ => …)
p.map(console.log) // 1, or Other(1) ???

"Monadic" promises and "non-monadic" promises can coexist, yes, but they are incompatible with each other in terms of assimilation. One would need to spec a second algorithm (possibly even using then) that would not recursively unwrap.

@dmitriz
Copy link

@dmitriz dmitriz commented Apr 23, 2018

@bergus I really can't see any problems.

If your of here conforms to the Pointed Functor spec, then other.of(other.of(1)) should not unwrap like in [[1]], so nothing happens.
Whereas if resolve is unrwapping, similar to Promise.resolve, then in your example it should unwrap twice down to creed.resolve(1). (Apart from resolve being a suboptimal naming choice as Promise.resolve does not actually have to resolve if run over a promise.)

And the general principle should be simple too. You have the infinitely unwrapping then and resolve from any library, one-step-unwrapping flatMap and non-unwrapping map and of again, from any library. Any time there is a promise-conformant object from any library (or theneable), it should be unwrapped by the unwrapping methods. The infinitely unwrapping ones will continue until they can't unwrap anymore. That would actually simplify the current spec in half 😀 Am I missing something?

@bergus
Copy link

@bergus bergus commented Apr 23, 2018

@dmitriz TBH, I can see that solution as well, and have long been a proponent of "monadic promises" and wanted to avoid recursive unwrapping from the get-go (in A+). I'm probably not the one who can argue well against this. Now we just have to convince the ES committee and the engine implementors that this is a good idea…

@dmitriz
Copy link

@dmitriz dmitriz commented Apr 24, 2018

@bergus I have found another validation by @briancavalier that it is possible to implement of and chain without breaking then: #97 (comment)

That seems to be what creed is doing, where also wrapping Promises in Promises is possible via of. Surprisingly, I have read people's comments opposing the mere possibility to have Promises wrapped into Promises. Apparently some older promise libraries were against it, again for unclear reasons. It just looks frustratingly obscure for such an important decision affecting so many people.

@emilypi
Copy link

@emilypi emilypi commented Aug 22, 2018

It's like I'm watching a chronological replaying of all the fundamental mistakes "pragmatic" developers make in their attempts to avoid learning something new. Brilliant!

@puffnfresh holy hell m8

@danpantry
Copy link

@danpantry danpantry commented Nov 5, 2019

This aged like milk. TypeScript is now becoming incredibly popular. Typed JavaScript was very fringe in 2013, but in 2019 it is the opposite and now we are stuck with the decisions made back in 2013 because someone didn't like typed languages.

That's unfortunate.

@dmitriz
Copy link

@dmitriz dmitriz commented Nov 5, 2019

now we are stuck with the decisions made back in 2013 because someone didn't like typed languages.

Maybe @domenic has something to say about it?

@kennknowles
Copy link

@kennknowles kennknowles commented Nov 14, 2019

I love every additional comment here so many years later. I never muted this thread. The best part is that I have a lifetime of it to look forward to, for two reasons:

  1. The things done wrong here are fundamental to programming/computation itself, so there will never be a time when it is not wrong. And probably never be a time when it doesn't adversely effect programming in Javascript/Typescript.
  2. Those who think the conclusions here are OK will never search it up. This thread is kept alive by those who encountered the wrongness and searched the web for what went wrong.

So for those arriving now and forever: Welcome. You are in good company.

@davidchambers
Copy link

@davidchambers davidchambers commented Nov 14, 2019

Like @kennknowles, I have never muted this thread. It had not occurred to me, though, that commenting here could provide pedagogical value. I now see this possibility.

Broken Promises† documents the problematic characteristics of promises: eagerness, cancellation, specialized API, error handling, and mixing exceptions with failures.

Please excuse the Medium link.

@dmitriz
Copy link

@dmitriz dmitriz commented Dec 21, 2019

https://www.reddit.com/r/programming/comments/ed7flb/functors_what_are_they/fbgjves/

The original Github issue on whether promises should be monadic is one of the most horrific examples of ignorance and closed-mindedness I have ever seen. #94

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.