6 ms·
> In a language without exceptions, anywhere you call something that can return an error, you have to explicitly do something with it, even if you're just retur
by bedobi 3y ago
> In a language without exceptions, anywhere you call something that can return an error, you have to explicitly do something with it, even if you're just returning it early (which you usually are)
No? You just have to indicate it may exist, which is desirable. When you get user by user id from the db, there may be no such user, or there could be a db connection issue. The function should indicate that and compiler should check that you're handling it, not pretend nothing can ever go wrong. (which is effectively the case when you're hiding the unhappy path with exceptions) And there is no such thing as "returning early". In functional programming there is usually a single return statement with a chain IO or Eithers or whatever chained together and they decide themselves where they short circuit, not one return statement for if there's an error and another for a happy path.
In my example, Either<Error, User> might be what's returned natively by a functional db library that doesn't throw exceptions
but in languages without exceptions, like Haskell, it'd look like this
queryItemByID :: IConnection conn => Int -> conn -> IO [[SqlValue]]
queryItemByID itemID conn = do
stmt <- prepare conn query
execute stmt [toSql itemID]
rows <- fetchAllRows stmt
return rows
where
query = unlines $ [ "SELECT fieldName, value FROM itemDataValues, itemData, fields WHERE",
"itemDataValues.valueID = itemData.valueID",
"and itemData.itemID = ?",
"and fields.fieldID = itemData.fieldID"
]
queryItemByID :: IConnection conn => Int -> conn -> IO [[SqlValue]]
> queryItemByID 5569 conn >>= mapM_ print
[SqlByteString "title",SqlByteString "From Stochastic Calculus to Mathematical Finance-Kabanov.pdf"]
No exceptions anywhere, and you don't have to do anything with any exceptions. You could just send this IO back up to the http layer of your app and turn it into a 400 or whatever on any error if you don't want to disambiguate between them or handle them.
- hot_gril 3y ago> You just have to indicate it may exist, which is desirable. When you get user by user id from the db, there may be no such user, or there could be a db connection issue. The function should indicate that and compiler should check that you're handling it, not pretend nothing can ever go wrong. (which is effectively the case when you're hiding the unhappy path with exceptions) You don't pretend nothing can go wrong. Somewhere up the call stack, you're handling the exception. If nowhere else, whatever webserver lib will send 500 if you didn't catch something. To give a concrete example with my daily work, we use C++ without exceptions for high-level code for some reason. Because practically every func we write returns an error, the result looks like this: RETURN_IF_ERROR(DoThis()); ASSIGN_OR_RETURN(const foo, DoThat()); RETURN_IF_ERROR(DoThis()); RETURN_IF_ERROR(DoThis()); in every func down the entire call stack. And it was worse before we finally overrode the people opposed to macros: absl::Status status = DoThis(); if (!status.ok()) { return status; } absl::StatusOr<Val> val = DoThat(); if (!val.ok()) { return val.status(); } ... What's the value in putting this macro on every single line, to remind me that everything can fail? I know it can fail. This is like Bart Simpson with the chalkboard. Functional programming languages could have exceptions too if they wanted. It's just syntactical sugar around errors.
- bedobi 3y agoI mean if your opinion is "somewhere up the call stack, you're handling that exception" and "if you don't, the framework will 500" means you're not pretending nothing can go wrong, then we will never agree, because I absolutely would characterize both of those as pretending nothing can go wrong, at the very least in context at the function call site. As for your code example, again, that is not analogous to the functional style. The functional style is you have an Either object no different to any other object. You do not if on the object, you do not early return the object in the sad case etc etc. Just throw all notion of all that out the window. You just pass it around without doing anything, or, if you only wanna do something in the happy case, you send in what to do using map, and then keep passing it around. There is zero requirement to handle the error like you seem to imply. Only at the outermost layer do you handle the error, and only if you wish to do so. (if you don't, just fold it into a 400 or 500 or whatever)
- hot_gril 3y ago> I absolutely would characterize both of those as pretending nothing can go wrong, at the very least in context at the function call site Agreed that within the function call site I'm pretending nothing can go wrong, but not within the entire program. I don't see what's unsafe about that. We're just now implying that anything can return an error instead of restating that fact every time, and instead you have to be explicit about catching it. About the functional programming... you're saying there's no if-else or early return in functional programming, but Erlang, a functional programming language, has if-else and exceptions ("throwables") that look like Java: https://learnyousomeerlang.com/errors-and-exceptions https://learnyousomeerlang.com/errors-and-exceptions. Under the hood it's one logical chain like you said, but we're using a lot of syntactic sugar on top. > if you only wanna do something in the happy case, you send in what to do using map, and then keep passing it around That's the kind of if-else early-return I mean, and it's probably what you want to do if you aren't using exceptions. My C++ code also (explicitly) passes the error around, albeit not in the functional way. In like 99% of the cases you are just passing the error up.