5 ms·
Isn't this like a `throw`, where the else is the catch/finally?
by al_al 3y ago
Isn't this like a `throw`, where the else is the catch/finally?
- themoonisachees 3y agoIt seems so, but the catch being restricted to the else block of this exact if block means it's considerably less expensive than a regular exception. additionally, most of the time when doing operations covered by this, you wouldn't really care about the type of your supposed exception (ie why bother throwing a endoffileexception when you know you're going to catch it right there), so for this use case specifically this gives you a free pass to say "go to error handling, i don't care about naming things because this is all extremely local"
- FeepingCreature 3y ago(Author) Yep, it's sort of like throw/catch at the expression level, but through a different mechanism that doesn't allow non-local unrolling. In exchange, it's a lot more performant.
- a1o 3y agoI like I don't have to add names for things and it's perfectly easy to follow, plus I still get things that happen when going out of scope that wouldn't happen if I used a go-to, I found it brilliant! Great work!
- 22c 3y agoI found the blog post a bit hard to follow but there doesn't seem to be a need for exceptions here. This seems more like if (cond) <do stuff> if (anothr_cond) my_else() else <do more stuff> end else my_else() end but with syntax sugar so that the nested ifs are tucked away (and perhaps with my_else() scoped to the parent if).
- FeepingCreature 3y agoExactly! The big idea is that usually the `else` will do a nonlocal exit, so we should try to rewrite this in the "flat/early-out style." But then if you're declaring intermediate variables, you end up with duplicate lines: auto var = op; if (!var.cond) return my_else; auto var2 = op2(var); if (!var2.another_cond) return my_else; do more stuff; There's this sort of very regular block in there, of "declare variable, test condition, nonlocal exit". So the idea was to combine the `if (!var.cond) return my_else` into one expression-level location, so it turns out as auto var = op? else return my_else; auto var2 = op2(var)? else return my_else; <do more stuff> // Or instead if (auto var = op?) { auto var2 = var.op2?; <do more stuff> } Then the question was, what's the best way to get both of those? And it occurred to me what I actually wanted was just a way to branch out of the if entirely, I didn't really care about preserving any values in the alternate case, I just wanted to tear the entire expression/scope down and do something else.
- _old_dude_ 3y agoMonad to the rescue Optional.of(op) .filter(v -> !v.cond) .map(v -> op2(v)) .filter(v2 -> !v2.cond) .map(/* do more stuff */) .orElseGet(() -> my_else)
- FeepingCreature 3y agoYep, in an expression language that's how you'd do it. However, we're an imperative language, and the advantage of `else` is that we can use an imperative construct, `if()`, to locate the else case in the code - so we don't need to handle it in the typesystem. That's an inversion from monadic languages, where types contain/emulate statements - in C-likes, statements contain expressions/types! So `breakelse` effectively is glue that lets us turn part of the type into a control flow, which is deliberately not represented in the typesystem. `Optional.case(:else: breakelse).X` and now in X, we're no longer dealing with an `Optional` at all. We're not "inside" the Optional as we would be with `map`; we've effectively everted the entire `Optional` using non-local flow from the `else` case.