The first one reads like somebody just found out about functional programming and functors and tried to "improve" a straightforward bunch of if statements. I can read the second program without having read the plain English description, but I definitely would prefer to have the comment for the first one. I think even in languages like Haskell I would prefer (just sometimes) to read just a straight if-elseif-elseif-else.
The whole thing about the way it's written with throwing/catching is a red herring anyway, you should just replace those with a different choice of if's. If you're feeling super adventurous, you can instead replace them with goto's, which is kinda funny; it would actually simplify the code, how often do you see that?
I think part of the problem is that Typescript doesn't have support for error-as-value baked into the language and pervasive in the ecosystem, so adopting that style isn't as ergonomic as it would be in a language that does. The equivalent in Rust would be far more clear and concise due to the ? operator and Result-types being ubiquitous.
Agree and it could be easier to read in my opinion if you deal with the exceptions first.
For example.
let v = Number.parseInt("a3", 10);
try {
if (Number.isNaN(v)) {
throw new Error("NaN");
} else if (v > 3) {
throw new Error("gt 3");
}
v += 1;
} catch (error) {
v = 3;
}
v += 1;
Or writing a "guard function" that throws ...
function throwIfNaNorGt3(v) {
if (Number.isNaN(v)) {
throw new Error("NaN");
} else if (v > 3) {
throw new Error("gt 3");
}
}
let v = Number.parseInt("a3", 10);
try {
throwIfNaNorGt3(v);
v += 1;
} catch (error) {
v = 3;
}
v += 1;
Tbh, to me the first one is easier to read. There's less jumping.
But both are terrible. It should just be a bunch of if (...) { ... } else if (...) { ... } else { ...} etc. with no mutation of variables (what are all those v += 1 for?).
Those v += 1 implement the exact specification given above:
1. Parse an integer N from a string.
2. If N is NaN, fail with an error. Otherwise, increment N by 1.
3. If N is > 3, fail with an error. Otherwise, increment N by 1.
4. If steps 1-3 failed, set N to 3.
5. Increment N by 1.
Those are quite strange requirements, and the resulting second code looks strange too, but... it faithfully and obviously correctly represents the given specification.
The example is strange and contrived. I couldn't torture the logic enough to demonstrate how readability breaks down with exceptions. Reading through the comments, I realise that even if the code is unreadable with exceptions, it can be rewritten to be readable.
I updated the article to highlight not only readability, but also compositionality. I also tried to draw parallels with Promises (which are also monadic).
I tried to target the article to intermediate programmers that may not already know most of the concerns raised in the comments, and I surely can't cover enough space on error handling without writing a book.
Thank you for the comments. I've learned more from reading the comments.
The whole thing about the way it's written with throwing/catching is a red herring anyway, you should just replace those with a different choice of if's. If you're feeling super adventurous, you can instead replace them with goto's, which is kinda funny; it would actually simplify the code, how often do you see that?