Hacker News new | past | comments | ask | show | jobs | submit login

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.




> 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".


It's important to separate coding mistakes from exceptions. For exceptions, I define my own classes derived from the Error object and I give each one a unique `name` property that way the error handler can decide how to handle the error based on its name. If the error happens on the server but is also meant for the client side (and is human-friendly), then I may add an `error.isClientError = true` property; then, in my catch handler, I know that these kinds of errors are safe to send to the client.

Of course, it takes some discipline to come up with a consistent and well defined error-handling strategy, but IMO it is not difficult. Also it's not a problem if a third-party library doesn't agree with your error-handling approach, it's often a good idea to re-create and re-throw errors from third-party libraries to provide more context anyway. That said a lot of JS libraries do use a similar approach as me for error handling, but sometimes they might use an error.code property instead of error.name but it doesn't really matter.

The worst is if a third party library throws plain strings, but that's also often not a huge problem either because my own logic can make sense of the context and re-throw a proper SomeThirdPartyLibraryError which incorporates the original error string; it's just a little bit of extra work.


> 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.


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".


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.


Putting a catch-all at the root doesn’t keep you from catching exceptions before they get there.


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

I use Either types in javascript. I coded them myself (I like mine better than those in the article, but I would wouldn't I?). I check it all with typescript, and it's perfectly typesafe, same as it would be in haskell or ocaml.

So Javascript is 100% a language that allows you to build abstractions like that.


> I check it all with typescript, and it's perfectly typesafe

/me waits for arguments to ensue


I've found exceptions are really good for getting relevant context since you can always get at least a backtrace. This is one bad thing about errors-are-just-values; you get nothing besides what the callee decides to put in the error. Especially with monadic error handling, where it's so easy to just pass the error on up, you can easily get a error value from some deeply nested call and no idea what went wrong.


> 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.

20 years ago, maybe. These days every browser has a debugger which will automatically take you to the exact location with full context at every layer of the stack and tools like Sentry will collect and aggregate those reports even from devices you don’t control.


I'm not talking about ability to debug. I'm talking about the code handling errors gracefully.

Also the world is a whole lot bigger than frontend dev.


This is an article about JavaScript where front-end work matters a lot directly and influences everything else. That widespread nature is also relevant to the question of whether you use the platform as intended or try to graft on semantics from very different environments. The latter approach has a track record of people spending more time maintaining ungainly hybrids than the imagined benefits saved.


I agree, I've never seen an article with "Monad" and "JavaScript" in the title propose anything even remotely approaching elegant, and very rarely do they even offer any functionality you don't get built-in once you're at async/await-level language support.

Yes, monadification makes it easier to construct new functionality out of tiny parts, I'm not arguing with that at all. What it costs though is more than performance and debugability, but just simple readability goes right out the window, as a rule.


Isn’t Promise a specialized Either? Seems like the async/await syntax could also easily be tweaked for monadic error handling. It allows you to either handle errors as values or as exceptions without forcing you to use blocks and explicit control flow.

Edit: removed ill-thought-out remark.




Applications are open for YC Winter 2020

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: