emmanueltouzery / prelude-ts

Functional programming, immutable collections and FP constructs for typescript and javascript
ISC License
377 stars 21 forks source link

Extra functions for dealing with Future<Either> #55

Closed benjstephenson closed 2 years ago

benjstephenson commented 2 years ago

(recreate #54 because I'm a n00b and borked it)

emmanueltouzery commented 2 years ago

ok, i'm sorry for the huge delay in answering. yes as you noted in your original PR, i'm trying to keep prelude-ts basic/simple. So my first reaction about the FutureEither concept is... and I'm aware people use EitherT with future types and so on... So I'm legitimately asking a question here.. My first reaction is that Future is already an either-like type. Because Future already has the success/failure concept builtin (since basic JS promises do).

If you have a failed future we'll stop the computation. Future is not Future<L,R> but Future<T> because the left type of the Future is enforced by javascript to by any. Actually prelude also offers Future.onComplete which hands you an Either<any, T> upon completion of the Future, which emphasizes this existing connection between Future and Either. So when I see Future<Either<L,R>>, I really see Future<any, Either<L,R>> and this makes me think that we are nesting errors in this scheme and I'm not sure what this brings us (and again, people are doing this, so obviously there's a point here).

Why not leveraging the error case provided by Future out of the box, why nesting this error handling in the future value? Maybe you want to keep future any errors for things like network errors, and then have business errors in the Either? But I'm not sure that gets you that much value? Does it?

emmanueltouzery commented 2 years ago

This article is definitely related: https://www.geekabyte.io/2018/05/thoughts-on-dealing-with-having-another.html

They cover three approaches:

  1. Future of Either (what you're suggesting)
  2. plain Future, using the Future error for error handling instead of nesting futures (what I mentioned)
  3. using the EitherT monad transformer

The conclusion of the article is "avoid #1 (leads to unnecessary convoluted and unwieldy code), prefer #3 if possible, otherwise use #2".

Of course it's just one article and it was written for the scala world, not the JS world. But this makes me think that my idea was reasonable, if monad transformers are out of reach for prelude-ts.

emmanueltouzery commented 2 years ago

I also see that Martin Odersky, the scala author, wrote a comment on that blog post I linked to, and mentioned that he thinks that #2 is the best solution. Generally speaking I'd say that prelude-ts aims to match the scala standard library as opposed to FP libraries on top of that (eg cats or scalaz), so that would comfort me in that idea.

emmanueltouzery commented 2 years ago

One final comment and then I'll let you answer :D so if you have an existing function that returns an Either that you want to call within a Future, I'd consider calling .getOrThrow() on that either within the future. That'll cause the future to fail and in the end you can call onComplete on the future and handle it again as an Either, for instance.

benjstephenson commented 2 years ago

Thanks so much for your thoughts. I suppose the main thing that puts me off using Future to represent the errors cases is that there's just no type info associated with it. Thinking about it, you're right in that in my head the Future failure encoded stuff like network errors or timeouts and such that I wouldn't really try to recover from, where as the Either.Left is more domain type stuff that I would want to match against to take some action on. In these cases, if we were just using the Future, then I'd have to handle all and not be able to get any exhaustiveness checking via something like a type union and switch statement. One thing I know I definitely like from the Either is that by looking at the type sig of a function I can see what error cases I need to deal with.

I guess what I was going for with this was the EitherT approach, just with the F fixed to Future to avoid a generic implementation. I'm in no way a fully fledged FP wizard though, so could have missed the mark. I did think about whether it would be better to have the FutureEither stuff just all static methods rather than an instantiated class but I'm not sure if that would be better or not; couldn't hurt to give it go though and see how it falls out.

emmanueltouzery commented 2 years ago

Ok, I gave it some more thought. I've also found a bug in the vavr project about that same idea: https://github.com/vavr-io/vavr/issues/2378

Generally speaking, I consider vavr and scala's standard library as the north stars of prelude-ts... In doubt do what they do.

So I think I'll reject this suggestion. However if you're motivated enough to make a prelude-ts-plus or prelude-ts-extra or whatever library, I'd be super happy to link to it from prelude-ts's main page!

There is another reason for that, from my point of view: I've been relatively disappointed with composition when dealing with Either. Specifically, Either<A, T> and Either<B, T> don't compose well (which you covered in your PR with a combining function). And that brings me to yet another alternative way to tackle the problem: typescript's or types.. Meaning yet another option could be Future<Err | Res>. Where you would at every step in the computation pattern match to check whether you got an error or not.

So yes... Trying to keep prelude-ts simple, confirmed by the decisions of the scala standard library and vavr, and compounded with my own experiences with Either... All combine to convince me not to include this in prelude-ts itself. But really, if you make a separate library on top of prelude to handle this, I'd be delighted to link at it from prelude's home page!

In the meantime, I'll close this, but feel free to comment more here!

benjstephenson commented 2 years ago

Nice, vavr is another great lib. Ok, I think all of your points make sense to me so thanks for the opportunity to talk it through. Re the Future<Err | Res> pattern, I do like the idea of using TS types but I wonder if, for some, it might be a bit jarring to have two was of dealing with disjunctions. I really do see a type union like that to be an idiomatic TS way of expressing and Either. Lots of people I work with appreciate the instance method style of prelude-ts so I try to stick with that a fair bit. I'd be keen to have a go at putting together an extension library for sure, and thanks for the offer for a link on the page! Thanks again for the chance to talk things through.

emmanueltouzery commented 2 years ago

people I work with appreciate the instance method style of prelude-ts so I try to stick with that a fair bit.

i think you can add your own methods to prelude classes through the javascript prototype mechanism. So I think you're not stuck here. I hope you're not.

benjstephenson commented 2 years ago

There’s the typescript module augmentation method too which is sometimes handy. I think you’re right that they would be good options to explore for extension methods.