| ▲ | galkk 3 days ago |
| Idk, to me that constant Result<User, Error>
looks extremely ugly and unergonomic, even just to type.I understand that this is complicated topic and there were a lot of strong opinions even inside of Google about it, but god, I miss absl::StatusOr and ASSIGN_OR_RETURN. Yes, it won’t work without preprocessor magic (and that’s why this article goes through heavy functional stuff, otherwise it just cannot work in language like C#), but it’s so easy and natural to use in base case, it feels like cheating. |
|
| ▲ | simonask 3 days ago | parent | next [-] |
| I think in C# the way to solve this is to have two separate types, `Ok<TValue>` and `Err<TError>`, and provide implicit conversions for both to `Result<TValue, TError>`. The static method approach showcased in the article is really long-winded. |
| |
| ▲ | oaiey 3 days ago | parent [-] | | Yes. Or even simpler, static imported functions. That is similar to how ASP.NET Core handles HTTP feedback. A very common and understood concept. With implicit type parameters this boils down to Ok(4) or BadRequest() |
|
|
| ▲ | 3 days ago | parent | prev | next [-] |
| [deleted] |
|
| ▲ | moogly 2 days ago | parent | prev | next [-] |
| Is this similar? https://github.com/amantinband/error-or What's some of the "preprocessor magic" that makes this[1] more ergonomic to use? [1]: https://github.com/abseil/abseil-cpp/blob/master/absl/status... |
| |
| ▲ | galkk 2 days ago | parent [-] | | Look at the long chains of methods in https://github.com/amantinband/error-or?tab=readme-ov-file#n.... The code looks quite unnatural. In google/c++ you can do much simpler, but with preprocessor magic. Example: absl::StatusOr<User> loadUserById(int userId) { ... }
absl::Status populateItems(User user, std::vector<Item>& items) {...}
absl::StatusOr<Item> findItems(int userId) {
ASSIGN_OR_RETURN(auto user, loadUserById(userId));
std::vector<Item> items;
RETURN_IF_ERROR(populateItems(user, items));
for (auto& item: items) {
...
}
}
ASSIGN_OR_RETURN and RETURN_IF_ERROR essentially preprocessor macroses, that expand, more or less into. absl::StatusOr<Item> findItems(int userId) {
auto userOrStatus = loadUserById(userId);
if (!userOrStatus.ok()) return userOrStatus.status();
auto user = *userOrStatus;
std::vector<Item> items;
absl::Status st2 = populateItems(user, items));
if (!st2.ok()) return st2;
}
No long and ugly method invocation chains, no weirdly looking code - everything just works. You can see real life example here: https://github.com/protocolbuffers/protobuf/blob/bd7fe97e8c1...Again, even inside Google there were docs that considered those macroses bad and suggested to write straightforward code, but I'm in the camp who considers them useful and, maybe, sole good use of preprocessor macros that I ever seen, as there are no other way to clearly and concisely express that in majority of languages. F# has something like that with Computational Expressions, but they are still limited. |
|
|
| ▲ | pyrale 3 days ago | parent | prev [-] |
| > I miss absl::StatusOr Sounds like you would rather have an `ErrorOr<User>` than a `Result<User, Error>`. Both are union types wrapped in a monadic construct. |
| |
| ▲ | galkk 2 days ago | parent [-] | | I wrote example above: https://news.ycombinator.com/item?id=46508392 My point is not the types/monadic constructs, etc (I love to do functional jerk off as a guy next to me, though), but that there are ways to keep code readable and straightforward without neither invocation chains DoOne().OnError().ThenDoTwo().ThenDoThree().OnError() nor coloring/await mess, nor golang-style useless error handling noise |
|