3 ms·
> I don't want to manually handle every single error case I get this all the time from people who are used to exceptions, and it's based on a lack of understan
by bedobi 3y ago
> I don't want to manually handle every single error case
I get this all the time from people who are used to exceptions, and it's based on a lack of understanding. If you get an Either<Error, User> from the db, you don't need to handle the errors in every method call in the service layer. You can just call map and that function will be applied in the happy case, no need to handle the Error case. Then, at the resource layer, you can simply
userService
.getUser(userId)
.fold({ error -> HttpResponse.400(error) },
{ user -> HttpResponse.200(user) })
As for Go for HTTP REST APIs, agreed that it simply isn't a good tool for that. In fact, I don't think Go is a good tool for anything - for pretty much every use case, there are objectively superior languages, lol. But that's a whole nother can of worms.
- hot_gril 3y agoIn 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). That's what I meant by handling. Otherwise you're using exceptions, where early-returns are implied unless you catch. In your example, probably getUser is having to handle errors too, albeit with Erlang's rather slick syntax. I've been on both sides of this, preferring explicit handling when I was writing systems code and preferring exceptions when I was writing web backends, so I understand how to use each. Exceptions are popular in high-level languages because that's actually what people want in those use cases. I also dislike Golang for plenty of other reasons. "Master of none" language like you implied, and late to the party with nothing special to bring other than pointless syntax differences.
- 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)
- deleted 3y ago[deleted]