Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Idea: Task #25

Closed
chriskrycho opened this issue Oct 6, 2018 · 4 comments
Closed

Idea: Task #25

chriskrycho opened this issue Oct 6, 2018 · 4 comments

Comments

@chriskrycho
Copy link
Collaborator

Spitballing here a bit: I've been thinking about a type named Future or Task as a possibly-useful thing to add to the library. This is interesting to me in that it's really useful to have some tools in the space that Promise lives in—namely, asynchronous operations and computations—but with different tradeoffs than Promise made.

For those who might not have thought about what Promise does differently from, say, Result or Maybe before, there are a bunch of differences, some necessary consequences of how JS works and some less so. The ones that immediately come to mind for me:

  • Promise combines what True Myth's types call map and andThen into a single function: then. In terms of type signatures, the way to think about it is that map takes a function whose signature is (t: T) -> U and gives you back a Maybe<U>, and andThen takes a function whose signature is (t: T) -> Maybe<U>, and gives you back a Maybe<U>. The same thing goes for Result. Promise#then, however, can take either one. That is, if you give then a function with the signature (t: T) -> U, you'll get back a Promise<U>, and if you give then a function with the signature (t: T) -> Promise<U> you'll also get back a Promise<U>.

    This is a choice made for convenience and a sort of accident of history rolled into one, and I'm not at all interested in relitigating it. It is what it is in JavaScript today. More interesting to me is whether we can provide a better interface on top of it.

  • Promise to have two types in play—the return type and the type of the error that will show up in the catch arm—but unfortunately, the catch argument is (correctly) typed as any in TypeScript. (Properly, given TS 3.0, we’d probably do unknown, but TS sensibly tries to avoid backwards-incompatible changes in the standard library types.) This is because Promise#catch has to capture any thrown exception along with any rejection created with Promise.reject.

    As far as I can see this is a fundamental problem facing any kind of Task.fromPromise function, but in principle it’s solveable. You just need a type that takes the any (or better, unknown) type handed to the catch function and converts it to the desired error E type: (error: unknown) => E.

One other note: like Promise itself, and unlike Maybe and Result, a Task captures a kind of computation you can’t (synchronously) exit from! The whole point is to model asynchronous behaviors.

@chriskrycho
Copy link
Collaborator Author

Spitballing some of the conversion functions here:

type Task<T, E> = Resolved<T, E> | Rejected<T, E>;

function fromPromise<T, E>(
  promise: PromiseLike<T>,
  resolved: (t: T) => Resolved<T, E>,
  rejected: (err: unknown) => Rejected<T, E>
): Task<T, E> {
  let task: Task<T, E>;

  promise
    .then(value => {
      task = resolved(value);
    })
    .catch(error => {
      task = rejected(error);
    });

  return task;
}

function toPromise<T, E>(task: Task<T, E>): Promise<T> {
  return new Promise((resolve, reject) => {
    task.match({
      Resolved: resolve,
      Rejected: reject,
    });
  });
}

Note that this is effectively sugar for Promise<Result<T, E>>, and maybe that’s the smart way to implement it?

@belfz
Copy link

belfz commented Oct 20, 2018

How about calling it Future instead of a Task? Task kind of reminds me of an IO monad, which defers all the computations until explicitly run (I'm taking a reference from the Scala world).

@chriskrycho
Copy link
Collaborator Author

@belfz yeah, I used Task throughout the discussion because it was easier to type, but note that my very first sentence here reads:

I've been thinking about a type named Future or Task

In terms of which to land on, I intend to spend some time thinking about the semantics of the operation, as I did in choosing the Maybe and Result names.

I don't actually particularly love Future or Task, though those have a lot of currency from their use in other languages. Task tends to have the connotations you note, and Future is a pretty weird description for the thing when a lot of times you're talking about what to do when it's no longer in the future. 😆

More to come!

@belfz
Copy link

belfz commented Oct 21, 2018

Ha, I see your point! I'm not a huge fan of Future, either. There's also this Deferred name (quite often used before Promise made it into the ES spec, I believe).

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants