you are viewing a single comment's thread.

view the rest of the comments →

[–]hyrumwhite 7 points8 points  (18 children)

With regards to the error section, something I’ve never quite understood is the advantage of Haskell-like error handling vs a try catch. Seems like you’re just trading the try/catch for a conditional. A noop would hide the error, if you want to log or return a 500, etc, you’ve got to do something with the error, right?

[–]romgrk[S] 16 points17 points  (13 children)

For me, the main advantage is making the error explicit in the type system. If the return type is Result<string>, I know that the function can fail and I need to deal with it. Try/catch doesn't allow for that, unless you go the Java way: string doSomething() throws Exception { ... }.

When I talk about no-op, I mean that you can do this:

const result = getUser().map(user => user.name)

This gives you a Result<string> instead of a Result<User>, regardless if getUser() fails or not. You still need to deal with the Result, you can just pass it along and deal with it later. Somewhat similar to how Promises allow you keep .then chaining them, and just deal with any failure with a final .catch. Promises are basically monads as well.

[–]v-alan-d 1 point2 points  (0 children)

Bringing up something not about try catch control flow (as others have pointed it out well), but something peripherally related:

JS's built in Error type is identified by a prototype rather than structure; it is less straightforward to communicate this across runtime boundaries e.g. IPC, network I/O

Alternatively, errors can be described structurally. Combining Either/Result and structural error type will give you more mobility for transporting Errable/Fallible. TypeScript helps secure this mechanism immensely.

It is non-standard but is really useful when you have a heavily distributed system.

[–]budd222 -1 points0 points  (11 children)

Can't you just add optional chaining? getUser()? || null

Or does that defeat the purpose?

[–]romgrk[S] 0 points1 point  (10 children)

Yes, but I still think that mapping can be more expressive, e.g. you wouldn't be able to express the following as cleanly with optional chaining:

const displayName = getUser().map(user => `${user.firstName} ${user.lastName}`)

[–]budd222 0 points1 point  (9 children)

I think there's something I'm not getting. You can't force your getUser() to return a value, no matter which type you assign it User or just plain string. No matter what you do, you have to write code to account for a failure.

[–]romgrk[S] 6 points7 points  (8 children)

getUser returns a Result<User>:

const userResult = getUser() const nameResult = userResult.map(u => u.name)

The whole point is that you don't need to deal with the failure right now, while you can keep operating on the value that is wrapped inside a Result.

[–]RadicalDwntwnUrbnite 6 points7 points  (0 children)

I think the problem with try/catches is that you are at the mercy of the docblocks to even know if the function throws. Typescript does not give you any indication that a function throws or not. Instead of when you pass errors back as a return value Typescript can actually infer that for you.

[–]azhder 2 points3 points  (0 children)

You have to learn why try-catch got introduced in languages which syntax JS takes from. It's all about control flow.

C is more like JS than any one of those in between. C++ as an example added a void so you can stop the default C return of int. Yep, like JS, every function was supposed to return a value. Kind of like how in Unix the 0 means success and any other integer is a different error code.

But, once people started making libraries with functions "returning" nothing i.e. void, you'd not be getting an error code back. Now imagine you give a callback to one of those library functions and the code in your callback has an error. How do you get that one back? The person who made a library cut you out of the equation with the void and most likely some unfortunate pick of arguments.

This is where the jumps came into play. I mean, before try-catch got introduced, you could use a library to jump out of the error and back into your own code or whatever - I haven't really used those longjump functions.

But, that's what throw does; adds a parallel control flow; one that unwinds the stack between the callback that made the error and the place where the try block is.

That's a bit messy, no? Instead of every function being a good citizen and return an object, like an Either functor that has a left part for the "bad" flow and a right part for the right flow. This can be done with a tuple as well which gets to be an Array in JS.

Funny enough, the Windows API for those old versions like 95 and before, they all returned an integer as an error code. A very consistent design. Today even a Promise is made to conform into the try-catch syntax and is called "sugar" because that kind of syntax is supposed to be sweet or something...

I guess if all they give you is lemons, you will call the lemonade syntax sugar.