This website should probably start with this. Instead it’s a bunch of like, modern synergy-ese.
This website should probably start with this. Instead it’s a bunch of like, modern synergy-ese.
How does EffectTS improve upon that?
(Sum-)Types would of course be helpful in making it clear that the function can sometimes return an instance of some specific Error-subclasses of course.
Now I assume I could do something like that in plain TypeScript also, return error-instances of different error-subclasses and declare them to be possible return-types, and then handle them in the caller or its callers somehow.
Anybody getting the error-result could not pass it or return it to anybody else whose type does not expect an error-instance.
Is there something even better with EffectsTS?
Seems like it's mostly trying do similar things to what Haskell or StandardML does, but in Typescript.
With Effect (and ideas like it), you can treat error conditions as normal expressions, without special control flow. You can reason about the error conditions the same way you reason about the successful data flow. You can treat your own Error subclasses just like any other value type, with the same kind of conditional logic in the same flow. And you can do that without looking deeper into the call stack to understand where errors come from, because errors flow exactly where values flow.
Because errors and values are in the same flow, you can compose things in ways you couldn’t (or at least wouldn’t typically) with separate error control flow. You can build complex logic by composing simpler fundamentals, in a way that’s more declarative and self-documenting. Your error instanceof checks can be combined with map/filter/whatever else just like you might use with success values, and they can be mixed so that recoverable error conditions are expressed even more like success conditions.
If you’re into types, all of the same benefits apply at the type system level. You don’t have to care about types to enjoy these benefits. If you don’t care now but embrace types in the future, you’ll just get more of the same benefits in that hypothetical future.
It's often said that checked exceptions are the worst mistake in Java and I believe that.
To be specific consider the case of a class that implements FetchSomeData which fetches some data (could just as well be a function with parameters encoded inside it as a closure) which could come from many sources such as
* hard coded in the class
* read from a file
* fetched via http
* fetched from Postgresql or CouchDB or ...
the one thing these have in common is they can fail in very different ways. If you want to take encapsulation seriously here the right way to do it is to expose certain semantics of the error such as * Could the user avoid this problem by changing their inputs?
* What do we tell the end user? What do we tell the sysadmin?
* Is it likely that this problem will clear up if we retry the request in five minutes?
* Is attempting to use this object likely to cause worsening data corruption
and such. In Java we get the very week beer of people creating a large number of exceptions like FailureinSubsystemSeventeenException and sprinking thousands of methods with throws clauses and doing a lot of error-prone catching and rethrowing to please the compiler but if you really are serious about error handling you know you could get a RuntimeException because something wrote a[15] on an array that has only 12 elements and you still need to catch those anyway.So far systems that use something like Either[Result|Exception] aren't based on an ontology of failure but my experience is that code written like that tends to have the same problems you have with checked exceptions: harried by the compiler people often omit error handling code to the maximum extent that they can. From working with both kinds of code bases I'd say that code bases worked on exceptions have the glass half full and handle errors properly more than they do improperly, these monad-based systems are the other way around.
Java has poor ergonomics for these, and in my opinion languages like Rust have better ones and that's why they manage with Results.
Otherwise your code is just littered with try/catch/throw new RuntimeException(ex).
There's lots to Effect beyond error handling, I can imagine it can seem a bit overwhelming.
The great thing is that you can cherry pick what you need.
Concurrency, runtime, tracked errors, functional composition, dependency injection, etc there's lots of problems that the ecosystem solves.
It's hard to say which of these features is particularly more important.