20 ms·
Elegant Error Handling with the JavaScript “Either” Monad
- diggan 7y agoThought 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.
- quickthrower2 7y agoIt reminds me of what svg text rendering looked like in 2001 using IE and the svg plugin from adobe.
- cheeze 7y agoThe whole site is incredibly hipster
- lilbobbytables 7y agoI don't _think_ I'd call it hipster - hipster has a lot more white. This actually reminds me a LOT of making websites around 2006 - 2012.
- signal11 7y agoThe 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 7y agoIt'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 7y agoI 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.
- Retra 7y ago>font that looks like it's been printed on paper with poor ink Actually, I think it is intended to look like mechanical typewriter output.
- jtdev 7y agoThe 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.
- cousin_it 7y agoAgreed. I never understood the love for Either-based error handling in Haskell and other languages. It's such a bare bones system. If you add all the features that people want from an error handling system, like stack traces; and remove the misfeatures that nobody wants, like Either<IOError,Either<MathError,Result>> being a pointlessly different type from Either<MathError,Either<IOError,Result>>; you end up with (surprise!) try-catch.
- roblabla 7y agoI'm not familiar with Haskell, but Rust's `Result` type is the same idea, and I wouldn't want to trade it for try-catch. It supports backtraces[0], has a simple way to turn errors into a "catch-all" type[1], but it is significantly less complex since it reuses the basic return value mechanism a function has. If you want to exit the program because the error can't be handled (similar to an unhandled exception), rust has a different concept for it, panics, and any Result can be turned into a panic via `unwrap()`. IMO both are important: panics represent unrecoverable errors, while Results can be acted upon (for instance, if read() returns Err(EINTR), you'll want to retry the read) [0]: Granted, you need to write some boilerplate to get the backtrace on error creation. [1]: You can cast all errors to `Box<dyn Error>`, a heap-allocated dynamically dispatched Error type.
- Klathmon 7y agoI'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 7y agoReadable 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.
- deleted 7y ago[deleted]
- vishbar 7y agoScala's for-comprehensions (and Haskell's do-notation) help quite a lot as well.
- antisemiotic 7y agoAlso F#'s computational expressions.
- Vanit 7y agoThis 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 7y agoYou 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 7y agoWhat if you miss one of these exceptions?
- Spivak 7y agoThe 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 7y agoSo essentially Java checked exceptions but which also applies to descendants of RuntimeException?
- Spivak 7y agoYes! 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.
- tjansen 7y agoThe '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 7y ago> 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.
- bcheung 7y agoYes, Promises are similar to monads but they don't obey all the monadic laws; as a result they don't compose as well. This means that you're always "stuck in the weeds" of promises and can't write code that abstracts it away. Or that treats promises, observables, arrays, etc, exactly the same since they all have different "interfaces". In that sense JS is more complicated because other languages don't make you learn different syntax and APIs for each one.
- bpizzi 7y ago'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.
- e1g 7y agoIndeed. For a while we focused on the happy path/MVP, and I was amazed how quickly edge-cases start piling up and sinking the ship with endless fixing/debugging/rework tasks. Now, we start with failure-cases first, engineer for resilience, and only then think about features. In consumer tech, failure rates of 2-5% often will not even trigger PagerDuty, but in enterprise that gets you fired real quick.
- agumonkey 7y agoI wanted to coin a term for this. Failure oriented programming. State what it MUST NOT be, then the happy path will emerge.
- mpfundstein 7y agoVery interesting. Can you tell more about that idea? Could we formalize that even ?
- agumonkey 7y agoI guess you understood as much as I could envision it. Start your design phase by modeling all the forbidden state, errors, exceptions. Everything else is either 'good' or 'non critical'. to mock TDD a bit: Paranoid Driven Development But that's still a shower thought theory you know :)
- ishjoh 7y agoI 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.
- deleted 7y ago[deleted]
- yomly 7y agoAre 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 7y agoWith static typing: https://www.npmjs.com/package/minimal-result https://www.npmjs.com/package/minimal-result
- benawad 7y agoKISS
- deleted 7y ago[deleted]
- dustingetz 7y agoI 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.
- lngnmn1 7y agoIn a strict language (call-by-value semantics) the notion of a Monad makes no sense and degrades in merely a sum-type (an either-of algebraic data type). There is absolutely nothing "monadic" in an ordinary either-of type with functions (which will make it an ordinary ADT).
- giaour 7y agoI 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.
- shawnz 7y ago> 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 7y agoAnd 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.
- chrismorgan 7y agoError 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.
- agentultra 7y agoWhat is foolish about the naming? It's a Bifunctor and while often used for error handling in programs it actually has nothing to do with error handling. The naming, afaik, comes from the correspondence to the logical disjunction operator and so the constructors name the Left side of the disjunction and the Right side of the disjunction. Seems rather sensible to me.
- jupp0r 7y agoIf you use it for error handling, it takes a lot of mental energy to remember which side is the happy path and which side is the error path. Maybe (no pun intended), people should have used Either to build a Result type, but as it stands, raw Either is used everywhere by convention, creating confusion.
- turdnagel 7y agoI 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 7y agoThat 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.
- vikingcaffiene 7y agoAh yes long division of Roman numerals. Can’t count the number of times that’s come up...
- turdnagel 7y agoThis 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 7y agoSure. 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.
- dakom 7y agoSeveral 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
- Klathmon 7y agoI pretty much agree with you. I hate error handling in JS. It's messy, it's really REALLY hard to get right, every single project has to layer on their own ways of handling it since the only thing the language gives us is the ability to `throw` and `catch` (even the "standard error" object doesn't have anything other than a place to put a string). It's a giant mess, and I'd love to find a way out, but I think it has to come from the language itself. Trying to shoehorn JS into a fully functional language leads to a lot of scaffolding and "custom" piping to get it to work, and in 100% of the situations I've seen it done it's been harder to maintain the scaffolding (and wrap 3rd party code to work in the way you need it to) than it ever would be to manage exceptions. I'll admit to be fairly ignorant of the different ways of handling exceptions in other languages. For the most part I've really only experienced try/catch, multiple-returns (golang style), and whatever bastardization JS is at now (try/catch + .then/.catch + custom error objects) in production codebases. But that solution needs to be part of the language not bolted on in userland. Mainly so that you don't need to write all this piping to get the rest of the ecosystem to behave in the way you want/need. FWIW Rust's Result type is beautiful and seems to solve a lot of issues (from the very small amount of experience I have with it), but trying to use that in JS is only going to lead to trouble without some help from the language itself.
- vinylkey 7y agoI 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.
- zaroth 7y agoThat’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.
- ryanmarsh 7y agoThere’s all manner of creative ways to solve problems in code but if they aren’t idiomatic then, IMHO, they aren’t applicable. Code should be first and foremost understandable by humans and only incidentally for compilers.
- nine_k 7y agoThere's a point where long boilerplate becomes unmaintainable by humans. Approaches like this remove boilerplate in intuitive ways; look at the amount of code in the `Left` and `Right` implementations. They make code more understandable. C needs not to be the idiom for Javascript, despite syntactic borrowings made in 1994.
- marmada 7y agoThis approach doesn't remove boilerplate, the initial version is 8 lines, the final version is 6 lines. Except the final version has a lot of code hidden in helper methods, making it impossible to understand at first glance without reading those helper methods. (LiftA2, chain, Left, Right, Lodash helper methods). The initial version is also very clear, we have a set of method calls that execute sequentially, and if any of them fail, we go to the catch block and call showError. This indicates that all functions in the try block can throw an error. The final version goes through a lot more trouble yet I don't get any extra information by reading the code. Now I don't even know which functions are error prone because everything is a mix of weird functions like map, chain, and liftA2. The second version also seems to promote more cognitive load. Try-Catch is simple, it doesn't require any brain cells to understand. This approach required an entire blog post. It seems better to dedicate mental energy to more important decisions rather than a simple function that processes CSVs. I think I understand the benefits of functional programming when one is writing a compiler, but this seems to be a misuse of functional techniques.
- vga805 7y agoI 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?
- sansnomme 7y agoHere's a better formatted one: https://blog.logrocket.com/elegant-error-handling-with-the-javascript-either-monad-76c7ae4924a1/ https://blog.logrocket.com/elegant-error-handling-with-the-j...
- treis 7y agoIt'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.
- crooked-v 7y agoAlso, the text is too big by default.
- iainmerrick 7y agoAnother 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 7y agoYour statement is true for throwing exceptions, however you cannot catch exceptions in pure way
- iainmerrick 7y agoWhy 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.
- Stevvo 7y ago"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!
- ben509 7y agoYup. Pretty sure the most referentially transparent function will stop if you unplug the power.
- zygimantasdev 7y agoImagine 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
- jupp0r 7y agoI'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).
- taeric 7y agoI think the point is there is no inherent meaning. It is just two possible results. Could be an error, could just be another likely path. Will only be one of them, though.
- jupp0r 7y agoI agree that Either is more general than error handling, but it's widely used for that in practice. The original article uses it for that, too.
- marcosdumay 7y agoBut there is a difference in meaning. Left shortcuts, Right doesn't. It would be bad to name it after error handling, but the entirely meaningless names aren't great either.
- jayshua 7y agoLeft only shortcuts because of the implementation of the map function. There could just as easily be mapLeft and mapRight functions instead, allowing you to choose the shortcut behaviour.
- hombre_fatal 7y agoI don't think there's any documentation on Haskell's Either monad that doesn't explain that Right=Good even if it's only by convention. But its functions also has right affinity, so Right and Left cannot have equal weights. You have to decide why this result is in the right/default slot over the other one.
- ww520 7y agoPromise 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 7y agoI 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 7y agoNot many people get the hang of Promise style programming. People prefer async/await.
- bdorn 7y agothe 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.
- nielsbot 7y agoI 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 7y agoI 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.
- WC3w6pXxgGd 7y agoWhat's a monad?
- cryptica 7y agoI 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 7y ago> 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".
- acdha 7y ago> 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.
- rienbdj 7y agoWithout a type system complex monad stuff is very easy to mess up.
- leshow 7y agoI 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 7y agoI 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 7y agoTo 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 7y agoTo 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.
- z3t4 7y agoIt'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.
- thanks_ihateit 7y agoHa! I love the fact that both the hideous JavaScript/functional drivel and the garish, obnoxious font and page layout work together to produce a revulsion that has something everyone can hate. It's like the web v2.5 analog of an Excel spreadsheet typeset entirely in Comic Sans. A near total antithesis of "elegant" error handling.
- galkk 7y agoDropped reading article after one screen. Too hard to concentrate, especially for the code blocks font.
- inlined 7y agoWhere 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
- ncmncm 7y ago"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.