Hm, maybe I'm not seeing the point here, or maybe I'm just too old and crotchety. But it seems to me that Effect provides two things:
1. A way to have strongly-typed errors
2. A standard library, like a better HTTP library and better concurrency primitives.
I'm not particularly sold on 1) in isolation. If you want strongly typed errors, you can use vanilla TS: just use `type Result<T> = { type: 'result'; data: T } | { type: 'error'; message: string }` as a return type and you're basically there, modulo an if statement on the calling side.
On the other hand, 2) seems a bit more compelling. I do think it's true that JS could use a better standard library. But the cost of the standard library is that I now have to write my code in this extremely unusual way, and that seems like a bit too high of a cost to me. Effect.forEach (instead of for...of), Effect.runPromise (instead of await), Effect.andThen (instead of... nothing?) have, to my eyes, significant penalties in readability, debuggability and maintainability.
Something which gets me about these Effect methods is that very similar things can be accomplished with generators. I assume this is all generators under the hood. I personally would rather just write that code because once you’re familiar with it, it’s really not that difficult to work with.
I agree that a wrapper around generators is nice if it suits the situation (like async/await), and abstracting them is useful at times, but I’m not sure I’d want to pull in an entire library for it.
One thing, the Effect.runPromise and similar methods which seem like overkill are probably providing quite a bit more utility and reliability than it appears. It might offer good cleanup guarantees as well.
I should add that this is the equivalent of a hot take. If I looked closer I’m sure I’d change my mind, but I’m old and crotchety like you.
It reads a bit backwards indeed. Effect.Http.request.get().pipe(Http.response.json, Effect.Retry)? Why not Retry(get())?
This doesn't feel like a pipe, the pipe is mixing data pipeline operations with declarative configuration reusing the original operation, almost like the Config Builder pattern. Nothing wrong with that in itself, except it's obscured by calling it a pipe.
What will be retried anyway? The last step in the pipeline or the whole pipeline? Do you really want to retry a json decode or 4xx client side error? Ignoring this makes for a nice front page demo of short code, but when you start considering real scenarios, some of the same complexities of the vanilla ts version eventually creep up. The question is, do you want to solve those with straightforward, yet slightly more verbose, ts code, or by learning all the edge cases of the library.
If you are looking for something similar to only handle the difficulty of error and retry handling related to fetch, and are ok with being tied to React concepts, i can recommend TanStack Query instead. Thanks to the reactive nature, you just need to configure a query object, which then only returns data to you and re-renders when it is valid. Good stuff.
1. A way to have strongly-typed errors
2. A standard library, like a better HTTP library and better concurrency primitives.
I'm not particularly sold on 1) in isolation. If you want strongly typed errors, you can use vanilla TS: just use `type Result<T> = { type: 'result'; data: T } | { type: 'error'; message: string }` as a return type and you're basically there, modulo an if statement on the calling side.
On the other hand, 2) seems a bit more compelling. I do think it's true that JS could use a better standard library. But the cost of the standard library is that I now have to write my code in this extremely unusual way, and that seems like a bit too high of a cost to me. Effect.forEach (instead of for...of), Effect.runPromise (instead of await), Effect.andThen (instead of... nothing?) have, to my eyes, significant penalties in readability, debuggability and maintainability.