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

Folding #113

Open
puffnfresh opened this issue Jan 9, 2017 · 8 comments
Open

Folding #113

puffnfresh opened this issue Jan 9, 2017 · 8 comments

Comments

@puffnfresh
Copy link

From the Bifunctor, it seems a Promise has two states:

  1. Error value
  2. Successful value

I would like to "run" a Promise and act on each state:

/* forall x. */ Promise.of(x).fold(a => false, b => true) == true
/* forall x. */ reject(x).fold(a => true, b => false) == true
never.fold(a => true, b => true) == undefined
@briancavalier
Copy link
Owner

Hey @puffnfresh. I'm having trouble wrapping my head around use cases of it for a few reasons. Do you have any in mind that you can share?

Where I'm getting hung up is it seems fold for any non-settled promise would need to return undefined (like your never example). E.g.:

delay(1000, 'delayed').fold(a => true, b => false) == undefined

That seems to imply that folks would often find themselves wanting to call isSettled before using .fold. Or perhaps it makes the type more like fold :: Promise e a ~> (e -> b) -> (a -> b) -> Maybe b. Do you have any thoughts on other ways it could work?

Creed promises aren't lazy, since one of the original design goals was to be ES-Promise compatible while also implementing Fantasy Land. So, fold wouldn't so much "run" the promise, as synchronously observe it's state at the instant of call (which may be why you quoted "run" in the first place).

Is that what you have in mind?

@puffnfresh
Copy link
Author

puffnfresh commented Jan 9, 2017

Yes, I wasn't thinking right - it should always return undefined. I just want to use this for performing side-effects.

@bergus
Copy link
Contributor

bergus commented Jan 9, 2017

No, a promise has more than two states. It's not Either. It just allows to bimap over two of those states.

fold (like you described it) cannot return a b, as the e and a values will only be available asynchronously. At best, it could return a Promise Void b.

I still don't see your use case. Why not simply use .then(val => true, err => false)?

@briancavalier
Copy link
Owner

Thanks @puffnfresh. I think @bergus's question about .then is valid. People tend to use then for side-effects, but I'm interested to hear why you'd prefer an undefined-returning fold over ES then.

Thinking about it myself, I can imagine a few potentially interesting things about fold (I'm not sure all of these are actually good things, but they're at least interesting)

  1. The fact that it returns undefined is a reasonably clear signal of intent: you're going to consume the final error or value, and cause a side-effect.
  2. Because fold wouldn't return another Promise, the caller is assuming responsibility for any failure. An exception that escapes a function passed to fold could be made fatal (simply by creed's machinery not catching it and letting it hit the VM).
  3. The argument ordering is like bimap, further making then the outlier.
  4. Making the arguments required means you have to explicitly ignore the error by providing a function that squelches it.

Any thoughts on those?

@puffnfresh
Copy link
Author

@briancavalier yeah, those are pretty much my thoughts.

Particularly the first one, if I want to do side-effects, I don't want to have a value.

@briancavalier
Copy link
Owner

It looks like there's some precedent for an operation named fold in fluture and folktale, which in creed's case would return a Promise.

So, maybe we actually want 2 operations here:

Map either side of the promise to a new fulfilled promise:

Promise e a ~> (e -> b) -> (a -> b) -> Promise e b

Consume either side of the promise and perform side effects:

Promise e a ~> (e -> b) -> (a -> b) -> undefined

Any thoughts on those two, and why we'd favor the name fold for one over the other? And what would we call the other?

@briancavalier
Copy link
Owner

FWIW, I'm still in favor of at least the "perform side effects and return undefined" function. I'm possibly in favor of the "map error or value to fulfilled" function, but seems harder to envision use cases for it.

@briancavalier
Copy link
Owner

Here's a proposal:

when :: Promise e a ~> (e -> f) -> (a -> b) -> undefined
fulfill(x).when(e => console.error(e), x => console.log(x)) === undefined // logs x
reject(e).when(e => console.error(e), x => console.log(x)) === undefined // logs e
never().when(e => console.error(e), x => console.log(x)) === undefined // doesn't log, ever

when():

  1. Calls the first function when the promise rejects, and the second when it fulfills,
  2. Discards the result of calling either function
  3. Does not catch exceptions thrown by either function (since they're called asynchronously, these will be fatal, i.e. they'll crash Node.
    • Note that if either function returns a rejected promise, creed's existing unhandled rejection machinery will make that rejection fatal as well (since it's discarded and no one can ever handle it)

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

No branches or pull requests

3 participants