
Elegant Error Handling with the JavaScript “Either” Monad - fagnerbrack
https://jrsinclair.com/articles/2019/elegant-error-handling-with-the-js-either-monad/
======
cryptica
I don't find it elegant at all. I prefer to use native language features than
inventing unnecessary abstractions.

>> and we all intend to put that try…catch block in place. Really, we do. But
it’s not always obvious where it should go. And it’s all too easy to forget
one. And before you know it, your application crashes.

Actually this is a good thing, it encourages you to make your code into
hierarchical, tree-like structures. Then you have a try-catch at the root of
each tree. Some apps have only one root (e.g. the program's entry point),
others have a few (e.g. API endpoints on the server); it depends on how many
kinds of parallel/async workloads your system has to handle but generally this
should be obvious; if your system's entry points are not obvious to the point
that you can 'forget' to wrap try-catch blocks around them, it's typically the
sign of a bigger architectural design flaw.

>> Another thing to think about is that exceptions make our code impure. Why
functional purity is a good thing is a whole other discussion.

Yes, it's a whole other discussion in which many developers might take an
opposing stance. Personally, I think that co-locating state and logic is key
to achieving modularity and clear separation of concerns; OOP is likely the
best way to achieve that. I do think that functional purity is good in some
cases, but not the majority of cases.

A lot of the large functional systems that I've seen were highly
deterministic, highly robust spaghetti code; they are difficult to make sense
of and can become impossible to maintain.

~~~
monocasa
> I don't find it elegant at all. I prefer to use native language features
> than inventing unnecessary abstractions.

I prefer languages that allow you to build abstractions like this, rather than
being beholden to the standards body.

> Actually this is a good thing, it encourages you to make your code into
> hierarchical, tree-like structures. Then you have a try-catch at the root of
> each tree.

That leads to a complete lack of error handling, because you've lost all of
the relevant context of what you were trying to do. It's attitudes like this
that leave you with a dialog box that says "an unknown error has occurred".

~~~
mistersys
> It's attitudes like this that leave you with a dialog box that says "an
> unknown error has occurred"

For many errors, "an unknown error has occurred" is the right message, since
it's a bug. There's no action the user can take to fix it, and you should let
the user know to try again later or contact support. The message can then be
sent to your error reporting software so you can fix the bug.

Sure, you can explicitly call "An unknown error has occurred" dialogue on
every error case, but instead, I think the ideal tradeoff is to handle all
errors that you predict will happen regularly, (ex. 404, authentication
errors, etc.) on the value/monad level and handle errors you don't expect
happen via the throw level.

~~~
monocasa
For true errors like that, I agree with you, but the parent is basically
saying both "fuck monads" and "only handle errors in your top level modules".

~~~
kjeetgill
I do think theres a 'tiering' that needs to happen for robust error handling,
and I suspect you both agree more than disagree. Take the Go-ish handle
everything at a lowest level vs. a Java-y handle everything at the top entry
points (the parent's post exaggerated for effect) as two points for
comparison.

Disclaimer: This is a variant of my Java Exceptions vs. Go err conversations.
I'm working Either into it.

The Go-like approach can often force you to handle errors at the lowest level.
You often have _details_ of the error but you're without _context_ of the
_program_. An example is a zlib library; you don't want to handle the failure
of any particular `file.read(buff)` call but rather just fail at a higher
level `unzip_file(src, dest)`, and with context make choices in the program
from there.

Now you can do this in Go as well, you just manually unwind returning err to
the level above. It's verbose compared to exceptions (but more
explicit...ish). I suspect most programs will end up resolving things at about
the same level.

That's the thing with Either(), since it's doing the catching you clearly
_have exceptions_ to work with too. In terms of error handling the interesting
bit is what 'tier' you handle things at, the Either vs Try/Catch are like 80%
the same.

------
jupp0r
I've always thought that the Left and Right naming in the Either type was a
poor choice in Haskell. Left and Right don't convey any semantics at all and I
constantly need to spend mental energy on remembering which is the Ok and
which is the Error (speaking in Rust Result terms). Sad to see that people are
translating these naming mistakes into JavaScript.

The underlying error handling methodology however is awesome and I miss its
guarantees and composability in my daily C++ work (yes I know, there's Boost
Outcome, but they stripped it of everything useful before including it in
boost, as far as I'm concerned).

~~~
mannykannot
The way I remember is that the Romans associated the left with bad things,
with this ultimately making its way into English in the word "sinister" [1]

As this association was extended to left-handed people, its use nominally
perpetuates an ancient slur, but I think we can put that aside.

[1] [https://english.stackexchange.com/questions/39092/how-did-
si...](https://english.stackexchange.com/questions/39092/how-did-sinister-the-
latin-word-for-left-handed-get-its-current-meaning#39094)

~~~
ubertaco
Yeah, I've heard the mnemonic "an Either contains either the Right answer or
else whatever's Left", which is simple/punny enough to be memorable.

~~~
dmix
That perfectly captures what it does too. Nice one.

------
zaroth
That’s an _impressive_ amount of work to carry an error state around, and
check before each step if there is an error set.

I feel like this is a trivially simple idea which has been contorted by poor
nomenclature and lacking the proper syntactic sugar, which results in
significantly more cognitive load than should be necessary to achieve the
desired result.

Kind of like that sentence actually.

~~~
steego
You're very polite. I'm going to be more frank.

Monads like this are very problematic in the JavaScript world. I say this as
an F# programmer, and our community is not unfamiliar with Railway Oriented
Programming
([https://fsharpforfunandprofit.com/rop/](https://fsharpforfunandprofit.com/rop/))

Unlike F# or any other decent functional language, JavaScript doesn't have
very good constructs to guide and ensure the programmer is using these
constructs properly, let alone using alongside with existing error propagating
constructs.

What happens when you mix these with promises, or RxJs, or something else?

My advice would to be avoid using constructs like this for general purpose
error handling and use tried and true constructs for _Exceptions_ , which
should be _exceptional_.

Something like this isn't entirely useless, sometimes its useful to encode
errors in an abstract type when the errors become an integral part of the
problem domain. Sometimes errors aren't exceptional and you need a way to deal
with them that's somewhat performant. Even if that's the case, you need to be
careful with constructs like this because it's easy to use them wrong, which
might lead to scenarios where errors aren't properly thrown and propagated.

I'm not saying to never use them. I'm just heeding you should tread carefully.

~~~
nine_k
Exceptions are for exceptional stuff, that is, something you do not normally
expect. Their purpose is cleanup after a non-catastrophic failure.

OTOH you totally expect I/O errors and parsing errors while reading a CSV
file. You totally expect a mismatch when matching a regexp. Using exceptions
here is discouraged even by Martin Fowler, of all luminaries [1].

[1]:
[https://martinfowler.com/articles/replaceThrowWithNotificati...](https://martinfowler.com/articles/replaceThrowWithNotification.html)

~~~
mLuby
Huh, I thought it was the opposite:

>we use the term exception for expected but irregular situations at runtime
and the term error for mistakes in the running program that can be resolved
only by fixing the program.
[https://wiki.haskell.org/Error_vs._Exception](https://wiki.haskell.org/Error_vs._Exception)

~~~
nine_k
If you start a thread and it gets killed by OS, it's an exception. You cannot
predict when or where it happens, and normally it won't happen; there is no
error in your code that leads to this. You can still try to gracefully react
on a thread death situation in your code by catching an exception.

OTOH if you wrote a non-total function and it fails to proceed given some
arguments, this is an error. You should not try to somehow continue if it
happens; you should fix your program.

~~~
firethief
> If you start a thread and it gets killed by OS, it's an exception. You
> cannot predict when or where it happens, and normally it won't happen; there
> is no error in your code that leads to this. You can still try to gracefully
> react on a thread death situation in your code by catching an exception.

I don't see how this is different from when a file that's supposed to exist
doesn't. I guess it's a matter of degrees.

------
chrismorgan
Error handling in Rust works this way, and Rust doesn’t have exceptions. (It
has panicking, but that’s expressly designed not to be caught except at the
thread boundary.) For Rust, the pattern works _really_ well. Since Rust 1.0,
there have been additions to the language to improve ergonomics, like the _?_
postfix operator, which will return an error, or unwrap a successful value.

Fortunately, Rust didn’t go with Haskell’s foolish Either, Left and Right
naming for its error handling type, but gave the enum and its variants
meaningful names, obvious semantics and good methods to go with that, so that
the Result type has two variants, Ok and Err.

I write plenty of Rust and plenty of JavaScript. I find that various patterns
that work very well in Rust don’t work so well in JavaScript; sometimes
because I care about performance (runtime and memory) and porting the pattern
harms that; and sometimes because of how you can’t conveniently add arbitrary
methods to a type like you can in Rust due to its trait system, and so you’d
have to give up method call syntax in favour of a hodge-podge of function and
method calls. (This becomes a problem with all dynamic languages I know of;
this is perhaps most clearly seen in Python where functional programming
constructs are second-class citizens, in part because the likes of map are a
function rather than a method—well, and because of its lambda syntax.) Also
because you’re often working on existing projects that use the normal JS way
of doing things, rather than getting to build from scratch, which would more
readily allow you extravagances like trying out error handling models unusual
for the language.

~~~
lalaithion
Haskell's Either isn't intended to be used solely as Left = Err and Right =
Ok. It's supposed to work as an abstract concept of "one thing or another",
and the language designers have avoided choices that would canonicalize it
that way, such as introducing a MonadFail (Either String) instance.

One example of why you would buck the trend would be to use of Left = Ok and
Right = Err for a series of "retry" computations. Say you have a few functions
of type Either a String, which all get progressively slower, but which work on
progressively more advanced input. An example might be a series of regex
engines, each of which run slower but handle more and more advanced lookahead
syntax. You can then write something like:

    
    
        makeRegex :: String -> Either Regex ParseError
        makeRegex regexExpression = makeBasicRegex regexExpression >> makeLookaheadRegex regexExpression
    

This will return a Regex using a simple parser if it can, and if that function
fails (by returning a Right value!) it will go onto the next function and try
that.

~~~
steveklabnik
Fun bit of history: Rust _used_ to have Either. Eventually we added Result. At
some point, we looked at all the code that existed (ahhh, the things you can
do when you're young) and _nobody_ used Either, only Result.

Today, either lives on as a package:
[https://crates.io/crates/either](https://crates.io/crates/either) It gets a
lot of downloads because it is actually used by a few popular libraries:
[https://crates.io/crates/either/reverse_dependencies](https://crates.io/crates/either/reverse_dependencies)

~~~
remram
Either also lives on in the `futures` library, where it is used when you have
multiple things that can happen in your asynchronous task and you want to
stick to static types rather than returning a trait object.

[https://docs.rs/futures/0.1/futures/future/enum.Either.html](https://docs.rs/futures/0.1/futures/future/enum.Either.html)

------
bpizzi
'If we didn’t have exceptions, we would have to write a lot of if-statements
all over the place. [...] We can focus on the happy path.'

In my own experience in developing and supporting enterprise class software,
focusing on the 'dark path' is much more important than the happy path. My
mantra is 'first make it stable, then make it easy to change behavior while
remaining stable, then finally add that functionality that the customer says
is absolutely mandatory'. But this is very specific to BtoB with big players.

~~~
ishjoh
I would say this is why monads such as Either, and Optional, are so important.
They require the programmer to deal with the dark path or at least make it
explicit when there is a dark path. I first encountered these monads writing
Scala years ago, and although it takes some getting used to, I always use them
now in my Java day job to communicate the dark path.

~~~
shawnz
What advantage does this approach give over Java's checked exceptions, which
have support built right into the language already?

~~~
joshlemer
Not saying using Either is pure win, but I think a lot of the benefits of the
approach is that it's a lot more flexible for different actions you may want
to take in response to errors. If you can reify the "throwing an exception"
action as data (in this case, `Left(error)` then you can very easily for
instance collect a bunch of these errors in a list or something, or perform
other transformations. Or maybe you have a collection of data to perform work
on, and only on the data that was associated with an error do you want to
report, and the data that was not associated with an error, you want to
process normally. It may be hard to write code that's flexible enough for all
of these use-cases in a style that deals with errors by throwing exceptions up
the call stack, but your mileage may vary.

------
diggan
Thought Firefox was broken when I looked at the code samples, but then I tried
it again in Chrome and thought my graphic card was going crazy. Turns out the
author actually chose a font that looks like it's been printed on paper with
poor ink, for the code examples. Would have been better for readability to
leave the code examples to some more normal looking font, or at least just set
`font-family: monospace` and let the OS decide.

~~~
signal11
The code samples look okay for me (did the author tweak the stylesheets?), but
the text uses an 'engraved' look using CSS's text-shadow (like iOS Notes but a
bit worse because of the serifs) which makes it a bit hard to read.

This is where browsers' reader modes really help. Is Chrome the only one now
that doesn't natively support reader mode? Reader mode in Firefox made this
much easier to read.

~~~
simcop2387
It's likely that you just don't happen to have the font then, "Gabriele Light
Ribbon FG" here's a place to see samples of it.
[https://www.wfonts.com/font/gabriele-light-ribbon-
fg](https://www.wfonts.com/font/gabriele-light-ribbon-fg)

~~~
true_religion
I have the font, and it looks readable to me.

I actually just didn't notice any difference in legibility and rather focused
on the neat effect of the brushmarks around the div enclosing the code.

I kind of figured people would complain about this though---its an artistic
sight, and not everyone is open to form over function.

------
iainmerrick
_Another thing to think about is that exceptions make our code impure._ [...]
_A referentially-transparent function will always give the same result for a
given input. But we can’t say this about functions that throw exceptions. At
any moment, they might throw an exception instead of returning a value._

That’s just incorrect. If it’s a pure function, it would always throw exactly
the same exception given the same input. It would still be referentially
transparent.

Sure, it’s a pure function with two exit paths, which is more of a hassle and
possibly more error-prone to deal with. But it can still be pure, so you can
still get the benefits of pure (dynamically-typed) functional programming.

~~~
zygimantasdev
Your statement is true for throwing exceptions, however you cannot catch
exceptions in pure way

~~~
iainmerrick
Why not?

 _Edit:_ to make sure we’re on the same page, I interpret “pure” as no side-
effects and no non-determinism, so a call with the same arguments always has
exactly the same result.

What side-effect does “catch” have? Something that’s visible externally,
visible to the caller of the function doing the catch.

I suppose if you catch an exception and pull out the stack trace, that gives
you some extra information about the caller. Even that doesn’t make you
impure, though, if you treat the call stack as additional input.

------
Klathmon
I'm sorry but this just seems so hard to read and I feel like it would be even
harder to maintain.

I'm currently working in a codebase that pulled a ton of this functional style
into javascript and it's overwhelmingly complicated and difficult to follow
even for me who has a bit of "functional" programming experience (mostly
ocaml).

Maybe I'm just missing something or maybe i'm just dense, but I haven't ever
personally seen a situation where the complexity of setting up and using this
functional style in javascript has ever been more readable or maintainable
than the equivalent "traditional" imperative programming. It's one thing if
the language or ecosystem kind of enforces this and gives you a standard set
of tools to work from or helps with this style via language constructs, but
javascript is not that language.

For the exact example in the article, both the try/catch version and the
"nullcheck" version seem much simpler to understand (even if they are
admittedly a bit "ugly" looking). And while I get that the author is using a
simplistic example to help teach, it really doesn't seem like a more complex
example would change things.

~~~
the_duke
Readable functional programming imo really hinges on some essential basic
language primitives. Those are sum types, pattern matching, and restricting
mutability.

Forcing sum types onto a language that does not support them is bound to end
in awkwardness and reduced readability without providing the real benefits of
increased correctness.

This is a great example. std::option from C++ is another one.

~~~
vishbar
Scala's for-comprehensions (and Haskell's do-notation) help quite a lot as
well.

~~~
antisemiotic
Also F#'s computational expressions.

------
leshow
I write haskell and love typed languages, but I would never use anything like
this. The fact is that it's forcing a concept that can't be expressed in the
language properly.

And in the end you haven't gained anything, the functions that you use inside
the block can _still_ throw exceptions so you need a try/catch anyway.

It's cool as an exercise, but I would never use it practically (in js). In
other languages that fully support sum types and errors-as-values (like Rust).
I find it a joy to work with and like it much more than exceptions.

~~~
lacampbell
_I write haskell and love typed languages, but I would never use anything like
this. The fact is that it 's forcing a concept that can't be expressed in the
language properly._

As someone who first discovered these kinds of types in F# and then ported
them over to Javascript/Typescript - please explain why it can't be expressed
in the language properly. Other than syntax it feels no different to me. It's
been a great benefit to my programming.

Objects have always been able to do a lot of what ADTs do and vice versa.

~~~
leshow
To start, there's nothing linking the left and right variants together in the
implementation shown in the blog. In Haskell and F# (never written f# but I
assume) left and right are variants of a data type. In this blog they are just
2 disjoint classes. Those classes just happen to have the same functions
implemented, but there's nothing specifying that to be the case.

There's no support for sum types in js, or good support for pattern matching.
If you like to use your home-grown Either type in js then more power to you.
But there's no denying that you're shoehorning a concept into the language
that it doesn't have good support for.

~~~
lacampbell
_To start, there 's nothing linking the left and right variants together in
the implementation shown in the blog. In Haskell and F# (never written f# but
I assume) left and right are variants of a data type. In this blog they are
just 2 disjoint classes. Those classes just happen to have the same functions
implemented, but there's nothing specifying that to be the case._

In my javascript (well, typescript) version, I use an abstract base class and
override the rightMap, leftMap etc versions. These concrete classes are
internal, only the abstract one is exported.

 _There 's no support for sum types in js_

Using TSc is a linter, there 100% is support. I'd be happy to demonstrate if
you're interested. Though it's a bit beside the point, despite having access
to them I didn't implement them that way as I prefer method chaining.

 _or good support for pattern matching._

What pattern matching do you need for an either type? I enjoy pattern matching
for lists, but even when using languages with pattern matching I don't use
them for Option/Maybe, or Either. I was under the impression it was an anti-
pattern to use pattern matching where a suitable function existed.

 _But there 's no denying that you're shoehorning a concept into the language
that it doesn't have good support for._

I deny it. IMO you can create a perfectly usable Either Type in any language
that supports classes, objects and generics (well, maybe not C++). There's a
lot I miss in JS from Ocaml and F#, but monadic error types are not one of
them, because they're easy to implement and just as ergonomic.

There's more than two ways to skin a cat, and after going back and forward
between OO and FP for a while I realised I could accomplish the bulk of what I
wanted in both paradigms.

~~~
leshow
> In my javascript (well, typescript) version,

I'm critiquing the implementation shown in the blog, not your hypothetical
implementation in typescript that I've never seen.

> What pattern matching do you need for an either type?

In nominal type systems like Rust/Haskell/F# you want to be able to match on
the constructor. In typescript where '|' means something slightly different
you need to fake a type constructor by adding a type: "variantname" property

> I deny it. IMO you can create a perfectly usable Either Type in any language
> that supports classes, objects and generics (well, maybe not C++).

Javascript doesn't support those things.

~~~
lacampbell
_I 'm critiquing the implementation shown in the blog, not your hypothetical
implementation in typescript that I've never seen._

That was not what you originally wrote:

 _The fact is that it 's forcing a concept that can't be expressed in the
language properly._

Your claim was that it cannot be expressed in the __language __properly, and I
am disagreeing.

 _In nominal type systems like Rust /Haskell/F# you want to be able to match
on the constructor. In typescript where '|' means something slightly different
you need to fake a type constructor by adding a type: "variantname" property_

You didn't answer the question, what useful pattern matching can you do with
Either/Option? Give me pseudocode even.

 _Javascript doesn 't support those things._

A few jsdoc comments and tsc command, and hey presto - it does.

------
tjansen
The 'Either' monad looks like a JS Promise without the asynchronicity. What he
calls left/happy-path is 'resolved' in Promises, and right/sad-path is
'rejected'.

I understand that using Promises for error handling has some advantages in
very unusual situations. But for the most part, I am happy about the greater
readability and lower complexity that async/await offer.

~~~
lmm
> The 'Either' monad looks like a JS Promise without the asynchronicity.

Congratulations, you're discovering the general concept of monads :). By
formalising that "looks like" you can write functions that operate generically
on both, and even on other constructs that conform to the same interface.

> I understand that using Promises for error handling has some advantages in
> very unusual situations. But for the most part, I am happy about the greater
> readability and lower complexity that async/await offer.

It's always the edge cases that get you. Naively-implemented async/await do
the right thing most of the time - and then they break down in exactly the
most complicated cases where you really can't spare the mental capacity to
deal with them doing something surprising.

If you implement async/await as lightweight syntax sugar over promises, then
you get the best of both worlds: you have lightweight syntax for the simple
cases, but can always fall back to the more explicit approach for the
confusing cases. Similarly, if you implement throws/try/catch as lightweight
syntax sugar over either, you can have the best of both worlds. Better still,
you can implement a _generic_ lightweight syntax sugar that's usable for any
of these kinds of constructions: "do notation" in Haskell, or "for/yield" in
Scala.

------
Vanit
This looks like it'd be a massive pain to debug unexpected exceptions in the
middle of a chain. Would you have to decompose the entire thing and step in
and out of every step? Yuck.

~~~
samhh
You should never have unexpected exceptions if you embrace static typing and
one of the many libraries that take this idea and do it better, for example
`purify-ts`. It also comes with methods included for statements that might
throw such as `JSON.parse`.

~~~
heavenlyblue
What if you miss one of these exceptions?

~~~
Spivak
The entire purpose of this school of error handling is that there are no
exceptions ever and unhandled errors are compiler errors.

It's weird in JS since exceptions to exist and you end up needing to build a
library of primitives that swallow exceptions and return them as errors. I
wouldn't do this in JS without tooling support because it's possible to miss
them but in principle the case you describe should never be able to happen and
that guarantee is enforced at the language level.

~~~
mort96
So essentially Java checked exceptions but which also applies to descendants
of RuntimeException?

~~~
Spivak
Yes! Personally I would argue a that this can end up being a little better in
practice since errors don't interrupt your program's flow and there's not an
implicit `if err jump` attached to every line but I suppose that's stylistic
preference.

~~~
heavenlyblue
It’s all nice and easy, but how would you handle OOMs in that scenario?

Or the cases with Rust’s assertions which kill the program?

The chance you’d need to rebuild a huge amount of software for that.

------
vga805
I would like to read the article but between the background and the code font,
I just can't do it. What is the thought behind this design decision?

~~~
treis
It's not even accidentally bad. They went through a significant amount of
effort to make it a great deal worse than simple black text on a white
background.

------
Stevvo
"A referentially-transparent function will always give the same result for a
given input. But we can’t say this about functions that throw exceptions. At
any moment, they might throw an exception instead of returning a value."

That's the premise of the article and it is demonstrably false. You throw
exceptions in a reproducible manner based on input, not a RNG!

~~~
zygimantasdev
Imagine a signature like this:

function oneDividedByX(x: Double): Double

oneDividedByX(2) // 1/2

oneDividedByX(3) // 1/3

and so on.. Except with zero. With zero you get an exception, however nothing
in function signature mentions about such result. You only expect a double

In Either case - it solves the problem, because the signature would be like
this:

function oneDividedByX(x: Double): Either[ComputingError, Double]

As for throwing exceptions - it can be done in pure way, but catching
exceptions cannot

------
shawnz
> A referentially-transparent function will always give the same result for a
> given input. But we can’t say this about functions that throw exceptions.

Why not? Given the same invalid input, the function will throw the same
exception. It seems to me like they only break referential transparency if you
purposely don't consider exceptions to be a "result"

~~~
roywiggins
And if you don't, you can just take your throwing function, wrap it in a try-
catch, and return errors. Voila, callers will have no idea it's really using
exceptions inside.

------
yomly
Are people employing this pattern without static typing - curious whether
people find the increased complexity in monad combination is worth the reduced
complexity of coding for the happy path

~~~
idbehold
With static typing: [https://www.npmjs.com/package/minimal-
result](https://www.npmjs.com/package/minimal-result)

------
ww520
Promise as a poorman's monad to do railroad-style programming works
surprisingly well, even for non-async functions. I often do,

    
    
        Promise.resolve()
            .then(dothis)
            .then(dothat)
            .then(doit)
            .then(domore)
            .catch(e => log.error(e))
    

It mixes well with regular try/throw.

~~~
WaxProlix
I did this a ton when building some mixed-sync/async stuff in TypeScript, and
tend to use it (or something like it, depending on the language) for any
situation that looks sufficiently similar. What are the downsides to this? Why
isn't it more common? (or maybe it is, and I'm just not reading the right code
to see it)

~~~
ww520
Not many people get the hang of Promise style programming. People prefer
async/await.

------
jtdev
The arguments here against try/catch are superficial and in no way compelling
enough for me to adopt this pattern for error handling. Seems to be a solution
looking for a problem... stop trying to be so clever - focus on writing clean,
readable, simple code.

~~~
lmm
> stop trying to be so clever - focus on writing clean, readable, simple code.

That's exactly what using either instead of try/catch does! try/catch are
magic language keywords that create invisible, surprising control flow. Either
is plain old code written in the normal language, with functions that follow
the normal rules.

~~~
jtdev
All language keywords are “magic” to some degree. The control flow that
try/catch creates has never been surprising or invisible in my experience.

~~~
cheeze
Fully agree. IMO this is the same argument as "the for loop is a magic
keyword" which just isn't true.

Try/catch makes it _incredibly_ clear what you're trying to do. Kids who
graduate college and get their first real job understand what a try/catch
does. A random contributor to an OSS project knows what try/catch does.

Meanwhile, I'd be willing to bet that not even 10% of programmers could
actually explain what a monad is.

I'm sure there is a place for this elegant error handling, but in most
codebases it seems like a pretty big complexification for not all that much
benefit. Sure, the code might even be "more correct" (whatever that means),
but if Samantha the intern can't pick it up rather quickly, it probably isn't
all that well suited for mainstream usage.

~~~
lmm
> IMO this is the same argument as "the for loop is a magic keyword" which
> just isn't true.

The for loop is indeed a magic keyword, though it's less surprising/magic than
try/catch; most of what for does could be done by a plain old function.

> Meanwhile, I'd be willing to bet that not even 10% of programmers could
> actually explain what a monad is.

Don't try to generalize prematurely. Considering Either on its own, it's
simpler than try/catch and can replace their use cases. If you'd started with
Either, try/catch would seem like the overcomplicated solution in search of a
problem that it is.

~~~
cheeze
> If you'd started with Either, try/catch would seem like the overcomplicated
> solution in search of a problem that it is.

But we didn't start with Either, which IMO is an incredibly important
distinction.

Bolting things like this onto the language after the fact isn't the same as
having first class support by default (like in say, Haskell)

~~~
agentultra
Haskell doesn't have special support for Either. It's a plain old data
structure with a few type classes. You can write Either in any language that
supports passing functions as parameter values.

It's taking the logical disjunction operator, `||` in many languages, and
sticking it in a data structure. You can now pass it around like a normal JS
value and combine it with other such values.

The benefit of this over exceptions is that for unexceptional situations you
don't end up throwing away the context of your computation if something takes
the "bad path." Instead of jumping to the exception handler and losing all of
your data your program can handle the situation at the site of the error where
it has the most context to solve the issue.

TFA wasn't advocating abandoning _try /catch_ \-- it was suggesting that for
non-exceptional cases it will make your code cleaner.

------
nielsbot
I feel like TypeScript would make this easy?

You can define your functions as returning Error or some other type:

    
    
        function something(...):Error|<something else> {
            ...
        }
    

Also, define `isError(...)`, like so:

    
    
        function isError(value: Error | any): pet is Error {
            return Object.hasPrototype( value, Error )
        }
    

You'd still have the if clauses, but maybe that's not so bad:

    
    
        function doStuff():Error|Result {
            let value = functionReturningValueOrError()
            if (value is Error) { return value }
    
            let value = otherFunctionReturningValueOrError()
            if (value is Error) { return value }
    
            ...
        }

~~~
remram
I also feel like this approach is a lot more valuable in languages that use
static typing, such as Rust, Java, and TypeScript. In a yolo-typed language,
it is just as easy to forget to handle the return type or the thrown
exception.

------
ncmncm
"Sum types" fill the opposite role in strongly- to that in in dynamic-typed
languages.

In Haskell, Rust, and C++, a "sum type" weakens type enforcement to provide a
data-flow path paralleling another for, we hope, a short distance, before the
paths peel apart at a convenient spot -- typically some sort of pattern match
construct.

In a dynamic-typed language like Lisp, Javascript, or Python, they appear in
more or less the same place, but their role is, instead, to impose some
discipline, to make visible what could otherwise be an implicit substitution
of one type for another.

------
vinylkey
I really like a lot of functional programming techniques in JS, but the
Left/Right style of error handling just always seems messy and unreadable to
me.

------
giaour
I don't see the utility when JS already has an option type that's deeply
integrated into the language: promises. The one advantage I could see in a
hand-rolled option would be forgoing the extra event loop ticks used with
promise resolution, but then you're limited to writing synchronous code.

In other languages, I normally use option types when things can fail in
unexpected ways, which almost always means I/O.

------
dakom
Several comments here are along the lines of "I haven't done much monadic
error handling but here's why it's bad."

That's silly because the benefits of this type of programming can't be grokked
until you use it on real projects.. it's honest-to-goodness not enough to look
at small examples and judge whether it's worthwhile or not.

There's a lot of this realization going around actually - for example Rust's
main raison d'être is basically: "look - we _can_ write safe c/c++, some of us
can even nail it perfect every time in small examples, but in large apps it's
inevitable that we'll all make mistakes"

Here's two issues with try/catch in real-world apps:

1\. You aren't forced to write it. It's simply possible to forget that a
function might fail (e.g. when that failure is defined as a side-effect). This
is even worse when there's no type system forcing you to think about it. We
all make this mistake!

2\. You can't easily distinguish between expected errors and unexpected errors
- and even where you can, dealing with that becomes a big mess.

Unexpected errors (like "out of memory", "computer is quitting", "electricity
is gone") - _should_ crash and burn and stop the app (unless you explicitly
have a way to recover from it - in which case it's actually an _expected_
error). Letting the app continue on where there's undefined behavior is
dangerous.

Expected errors (like "user not found", "wrong password", perhaps even
"network down") need to be dealt with. Every single time. Even in a function
where it might not happen 99.999% of the time - that one rare time where it
happens is especially hard to duplicate and fix.

That said, I agree with the comments that say it's a bit awkward in JS, and
much less effective without a type system forcing the right constraints. I
like using FP-TS[0] with typescript for extra help at "transpile" time, but
Sanctuary[1] (based on FantasyLand[2]) with the runtime checking turned on is
pretty incredible too.

With _some_ type system making sure that we're following the rules, monadic
error handling is definitely more elegant and safer than try/catch.

[0] [https://gcanti.github.io/fp-ts/modules/](https://gcanti.github.io/fp-
ts/modules/) [1] [https://sanctuary.js.org/](https://sanctuary.js.org/) [2]
[https://github.com/fantasyland/fantasy-
land](https://github.com/fantasyland/fantasy-land)

~~~
jerf
"That's silly because the benefits of this type of programming can't be
grokked until you use it on real projects.. it's honest-to-goodness not enough
to look at small examples and judge whether it's worthwhile or not."

But you can look at small examples and decide that the syntax noise isn't
worth it.

It's almost always a good idea to write "$LANGUAGE in $LANGUAGE" and not write
"Fortran in $LANGUAGE" or "Haskell in $LANGUAGE". Importing these idioms into
JS is pretty much the latter. You pay costs you don't pay in Haskell, and you
don't get advantages that you can get in Haskell. It shouldn't be a surprise
that the cost/benefit analysis for the constructs in JS can result in a very
different one than the one for Haskell. You can't just substitute the analysis
for Haskell and assume that it's all the same everywhere else.

It's not "elegant". It's importing what is an elegant idiom in another
language into a language where it's klunky, poorly-supported, and contrary to
the grain of the language. It isn't elegant in JS just because it's elegant in
some other language. It's exactly what it looks like: a klunky, choppy way to
program in JS that requires the creation of a ton of functions that only a few
outliers would normally think deserves its own function, making following this
code require a lot of jumping around in the code base.

And that's assuming it is what it says it is, which, as near as I can tell, it
isn't. If I'm reading this right, it doesn't seem to properly short-circuit on
errors properly, rather doing a rather odd thing where it tries to pass the
value along down the line, which is at the very least going to have
performance consequences and almost certainly major correctness consequences
as this scales up. It's actually not even an implementation of "monad", but a
sort of chopped up version to try to avoid creating even _more_ functions,
which is what is really necessary to correctly implement the "monad"
interface. In Haskell do notation, every single "<-" is actually an entirely
new function, nested inside the previous one, so:

    
    
        do
            thing <- whatever1
            morething <- failableLogic arg thing
            things <- mapM stuff [thing:arg2]
            return head things
    

If "failableLogic arg thing" fails, that function ends up _returning_ , and
the next two lines "don't execute", that is, the nested functions don't ever
get invoked. The "return head things" is actually a function nested in a
function nested in a function nested in a function. (I may be off by one.) I
think the core reason there's a lot of blog posts about how to "implement
monadic stuff" in all kinds of other languages and no code in the wild that
actually uses any of it is the requirement for all the nested functions. (You
usually can't "unnest" them either due to the fact you're almost always
closing over values from the previous functions.) It is astonishingly
inconvenient in languages that don't have the requisite features and support,
to the point you can fairly reliably guess that if someone has an
implementation that isn't staggeringly inconvenient to use, they've missed
something fundamental.

Even the languages that sorta kinda support this, don't, really. They encode
specific bits of functionality vaguely inspired by monadic handling, but out
of the Haskell family of languages I don't think I've yet seen a full, true
implementation of "monad" in general that anyone uses.

(When writing "$LANGUAGE in $LANGUAGE" isn't adequate for a task, it's almost
always the right answer to go get a new $LANGUAGE.)

~~~
0815test
> In Haskell do notation, every single "<-" is actually an entirely new
> function, nested inside the previous one ... The "return head things" is
> actually a function nested in a function nested in a function nested in a
> function. (I may be off by one.) I think the core reason there's a lot of
> blog posts about how to "implement monadic stuff" in all kinds of other
> languages and no code in the wild that actually uses any of it is the
> requirement for all the nested functions.

This "nested functions" pattern is precisely what promises/futures/async-await
does. It's just a different way of expressing the same things - "promises" are
just continuations, which are as general as do notation itself.

------
turdnagel
I am a big fan of returning arrays like [err, value] from async functions
(kind of like Go, only reversed) which can be destructured on one line. Yes,
it does end up with if/then statements but I find those cleaner than try/catch
semantics.

~~~
bcheung
That works well for simple programs, but if you write at scale, that style of
programming is very error prone and tedious.

It introduces a lot of noise into the program and makes it harder to follow
the logic.

If you don't lift yourself into higher levels of abstraction, it's much harder
to focus on more difficult problems. For example, long division with Roman
Numerals is more difficult than Arabic because there are simply more low level
details to worry about.

~~~
turdnagel
This appears to me to be a matter of personal preference rather than fact;
could you explain what you mean by "error prone and tedious"?

~~~
bcheung
Sure. The more things the programmer needs to worry about and explicitly
state, then there more pathways there are for error.

Using assembly language vs a higher level language illustrates this. There are
certain classes of errors that are eliminated by the language / compiler /
runtime. Examples could be garbage collection preventing you from have page
segmentation faults, or accidentally modifying the stack pointer and
destroying the return address.

Rich types like Maybe / Either offload the exception handling and allow you to
write your code for just the happy path while still having the program fully
support the unhappy path.

Compilers with pattern matching can give you errors when you have not handled
all possible inputs to a function (total function). If you just have the "if
err != nil" or null-check style code then it's easy to miss them.

By tedious, if you constantly have to type the same thing over and over ("if
err != nil") then it takes away time and mental energy from focusing on the
core problem domain.

Being able to express guard conditions to a function allow your functions to
be simpler and easier to understand.

It basically allow comes down to abstraction and letting the compiler /
language do things that it is good at and freeing the programmer from having
to worry about every single permutation. It's just not feasible for the human
mind to see every possible branch of a program.

------
dustingetz
I do this in ClojureScript (an actual functional language with macros to sugar
this type of thing) and it's really not a good time. Do it if you're cornered
like we were but we're moving away from it.

------
z3t4
It's a bless to be ignorant about all side effect and all the way something
can fail. It makes you very productive, and produce clean code, clean as in
not littered with resilience management.

------
inlined
Where functional case classes are available natively I love them. I wonder
though why the author didn’t use the Scala triple of Try[T] and its children
Success[T] and Error

------
rienbdj
Without a type system complex monad stuff is very easy to mess up.

------
galkk
Dropped reading article after one screen. Too hard to concentrate, especially
for the code blocks font.

------
bdorn
the design of this site is unreadable. skeuomorphism is a bad design pattern
(blotchy paper, really?) & is distracting from the content.

good thing firefox has a reader view. but really, this sort of design should
be gone by now.

------
benawad
KISS

