[HN Gopher] Result is all I need
       ___________________________________________________________________
        
       Result is all I need
        
       Author : rockyj
       Score  : 106 points
       Date   : 2025-10-25 11:40 UTC (6 days ago)
        
 (HTM) web link (rockyj-blogs.web.app)
 (TXT) w3m dump (rockyj-blogs.web.app)
        
       | anon-3988 wrote:
       | With the ? syntax in Rust results and exceptions are the same
       | thing. I posit that the former is superior. It is unfortunate
       | that results have worse performance but I don't see any reason
       | why. Results that bubbles up all the way ought to be identical to
       | an uncaught exception.
        
         | rcxdude wrote:
         | Exceptions can tradeoff happy-path performance for more
         | overhead on the slow path. For example, an exception
         | implementation can make it so that callers can assume the 'Ok'
         | result always appears, because an exception causes a seperate
         | unwinding mechanism to occur that walks the stack back,
         | bypassing that entirely. In contrast every caller to a function
         | that returns a Result must have a branch on that result, and
         | this repeats for each part of the callstack.
         | 
         | This also means that exceptions can have stacktraces that only
         | incur a cost on the unhappy path and even only if that
         | exception is uncaught. While if you want a trace for a bad
         | Result you are going to be doing a lot of extra book-keeping
         | that will be thrown away
         | 
         | In general I agree that Results are the better abstraction, but
         | there are sadly some tradeoffs that seem to be hard to
         | overcome.
        
           | thinkharderdev wrote:
           | This depends a lot of what you are using exceptions for. I
           | think in general the branch on Ok/Err is probably not
           | meaningful performance-wise because the branch predictor will
           | see right through that.
           | 
           | But more generally the happy-path/error-path distinction can
           | be a bit murky. From my days writing Java back in the day it
           | was very common to see code where checked exceptions were
           | used as a sort of control flow mechanism, so you end up using
           | the slow path relatively frequently because it was just how
           | you handled certain expected conditions that were arbitrarily
           | designated as "exceptions". The idea behind Result types to
           | me is just that recoverable, expected errors are part of the
           | program's control flow and should be handled through normal
           | code and not some side-channel. Exceptions/panics should be
           | used only for actually exceptional conditions (programming
           | errors which break some expected invariant of the system) and
           | immediately terminate the unit of work that experienced the
           | exception.
        
       | _ZeD_ wrote:
       | gosh...                       try {                 val user =
       | authService.register(registrationRequest.email,
       | registrationRequest.password)                      return user
       | } catch (exception: Exception) {                 // log exception
       | throw exception             }
       | 
       | no, no, no!
       | 
       | the _whole_ point of the exceptions (and moreso of the unchecked
       | ones) is to be transparent!
       | 
       | if you don't know what to do with an exception _do_ _NOT_ _try_
       | _to_ _handle_ _it_
       | 
       | that snippet _should_ just be                   return
       | authService.register(registrationRequest.email,
       | registrationRequest.password)
        
         | rockyj wrote:
         | I agree, this was just a sample code to show how usually
         | imperative if / else / try / catch code is written. What is
         | also possible is we catch the exception, log it and throw
         | another one.
        
         | CGamesPlay wrote:
         | I'm gonna plug my favorite note on this topic:
         | https://ericlippert.com/2008/09/10/vexing-exceptions/
         | 
         | Both snippets suffer from being too limited. The first, as you
         | point out, catches too many exceptions. But the second.... What
         | happens if the email address is taken? That's hardly
         | _exceptional_ , but it's an exception that the caller has to
         | handle. Your natural response might be to check if the email
         | address is taken _before_ calling register, but that 's just a
         | race condition now. So you really need a result-returning
         | function, or to catch some (but probably not all) of the
         | possible exceptions from the method.
        
           | noduerme wrote:
           | The way I usually structure it is that the only exception
           | would be some type of failure to connect. Any actual error
           | thrown by the service comes back as a result.error, and any
           | failure (like email address taken) comes back as result.fail.
           | This way you can separate it into (1) connection problem, (2)
           | backend error/bug/database offline/etc, (3) both of those are
           | fine but the backend doesn't like the input.
        
       | jillesvangurp wrote:
       | Since this looks like Kotlin, worth pointing out that there is a
       | kotlin class in the standard library called Result. I've been
       | using that for a few things. One place that I'm on the fence
       | about but that seems to work well for us is using this in API
       | clients.
       | 
       | We have a pretty standard Spring Boot server with the usual
       | reactive kotlin suspend controllers. Our api client is different.
       | We were early adopters of kotlin-js on our frontend. Not
       | something I necessarily recommend but through circumstances it
       | was the right choice for us and it has worked well for us in the
       | last five years. But it was a rough ride especially the first
       | three of those.
       | 
       | As a consequence, our API client is multiplatform. For every API
       | endpoint, there's a suspend function in the client library. And
       | it returns a Result<T> where T is the deserialized object (via
       | kotlinx serialization, which is multiplatform).
       | 
       | On the client side, consuming a result object is similar to
       | dealing with promises. It even has a fold function that takes a
       | success and error block. Basically failures fall into three
       | groups: 1) failures (any 4xx code) that probably indicate client
       | side bugs related to validation or things that at least need to
       | be handled (show a message to the user), 2) internal server
       | errors (500) that need to be fixed on the server, and 3)
       | intermittent failures (e.g. 502, 503) which usually means: wait,
       | try again, and hope the problem goes away.
       | 
       | What I like about Result is making the error handling explicit.
       | But it feels a bit weird to client side construct an Exception
       | only to stuff it into a Result.error(...) instead of actually
       | throwing it. IMHO there's a bit of language friction there. I
       | also haven't seen too many public APIs that use Result. But that
       | being said, our multiplatform client works well for our use.
       | 
       | But I can't expose it to Javascript in its current form; which is
       | something I have been considering to do. This is possible with
       | special annotations and would mean our multiplatform client would
       | be usable in normal react/typescript projects and something I
       | could push as an npm. But the fact my functions return a Result
       | makes that a bit awkward. Which is why I'm on the fence about
       | using it a lot.
       | 
       | So, nice as a Kotlin API but good to be aware of portability
       | limitations like that. You would have similar issues exposing
       | Kotlin code like that to Java.
        
         | rockyj wrote:
         | Thank you, a very insightful comment :) As a side note, my
         | latest post (on the same website) is on "reactive" Java /
         | suspend functions in Kotlin.
        
         | rileymichael wrote:
         | > But it feels a bit weird to client side construct an
         | Exception only to stuff it into a Result.error(...) instead of
         | actually throwing it
         | 
         | yep, `kotlin.Result` constraining your error type to
         | `Throwable` is a real headache as it forces you to still model
         | your domain logic via exceptions. it also means people can
         | still accidentally throw these exceptions. not to mention the
         | overhead of creating stack traces per instantiation unless you
         | disable that on every subclass.
         | 
         | i recommend using https://github.com/michaelbull/kotlin-
         | result?tab=readme-ov-f... (which has a nice breakdown of all
         | the other reasons to avoid `kotlin.Result`)
        
       | phplovesong wrote:
       | This is why Haxe is awesome. You can target a sloppy langauge and
       | still get the benefits os a ML-like typesystem.
        
       | paduc wrote:
       | For the typescript world, there is neverthrow[0] which offers a
       | similar Result type.
       | 
       | [0]: https://github.com/supermacro/neverthrow
        
         | pshirshov wrote:
         | Promise<Result<number, string>>
         | 
         | everywhere or                 type EitherT[F[_], E, A] =
         | F[Either[E, A]]
         | 
         | and then                 def dosomething(): F[String, Number]
         | 
         | ?
         | 
         | Isn't this beautiful: https://github.com/7mind/distage-
         | example/blob/develop/bifunc... ?
        
           | RedNifre wrote:
           | Why does it have Either? Doesn't TypeScript have "A | B"
           | style sum types?
        
             | pshirshov wrote:
             | Either is biased, union is not.
             | 
             | Probably we should say "union" instead of sum, as
             | typescript unions are not discriminated. string | string in
             | typescript is exactly the same as just string, while
             | Either[String, String] is a type which is exactly a sum of
             | two string types. Plus Either is biased towards R, the
             | happy path value.
        
       | pshirshov wrote:
       | Ah, Either. Didn't recognize you from the first glance.
       | 
       | Now we need to invent do-notation, higher kinds and typeclasses
       | and this code would be well composable.
        
       | tyteen4a03 wrote:
       | Smells like something that Effect-TS is designed to solve in the
       | TypeScript world.
        
       | flaie wrote:
       | Good article.
       | 
       | Maybe you could look up the Try monad API (Scala or Vavr works in
       | Java + Kotlin), by using some extra helper methods you can have
       | something probably a little bit lighter to use.
       | 
       | I believe your example would look like the following with the Try
       | monad (in Java):                 public UserDTO
       | register(UserRegistrationRequest registrationRequest) {
       | return Try.of(() ->
       | authService.userExists(registrationRequest.email))
       | .filter(Objects::isNull, () -> badRequest("user already exists"))
       | .map(userId -> authService.register(registrationRequest.email,
       | registrationRequest.password))           .get();       }
       | 
       | The login() function would be using the same pattern to call
       | authService.verify() then filtering nonNull and signing the JWT,
       | so it would be the same pattern for both.
        
       | lachenmayer wrote:
       | Stick your services into the type too, and you have `Effect`[0],
       | the ultimate monad :)
       | 
       | [0]: https://effect.website/
        
       | topspin wrote:
       | I've been toting around and refining a Result<> implementation
       | from project to project from Java 8 onwards. Sealed classes in
       | 17+ really make it shine.
       | 
       | I wish Oracle et al. had the courage to foist this into the
       | standard library, damn the consequences. Whatever unanticipated
       | problems it would (inevitably) create are greatly outweighed by
       | the benefits.
       | 
       | I've written Pair<> about a dozen times as well.
        
       | oweiler wrote:
       | Use the builtin Result class and runCatching/fold and be done
       | with it. Yes, it has shortcomings but works well enough in
       | practice.
        
       | byteshiftlabs wrote:
       | I love how everyone here shares real experience with Kotlin and
       | Result, it's cool to see different views that actually teach
       | something.
        
       | time4tea wrote:
       | Result<T> is a built-in in kotlin, but this enforces that the
       | error type is a Throwable
       | 
       | If you fancy that an error could be just a type, not necessarily
       | a Throwable, you might like Result4k - it offers a Result<T,E>
       | 
       | https://github.com/fork-handles/forkhandles/tree/trunk/resul...
       | 
       | disclaimer: I contribute to this.
        
         | RedNifre wrote:
         | Nice, what's the KMP plan there?
         | 
         | We currently use https://github.com/michaelbull/kotlin-result ,
         | which officially should work on KMP, but has some issues.
        
         | LelouBil wrote:
         | In Kotlin, my go-to library is https://arrow-kt.io/
        
       | greener_grass wrote:
       | Result is great but it ideally needs extensible union types
       | (polymorphic variants) plus exhaustive pattern matching to work
       | well.
        
       | iandanforth wrote:
       | Not a fan. Code branches and this is Good Thing(TM). Result
       | violates the single responsibility principle and tries to make
       | what are distinct paths into a single thing. If your language has
       | exceptions and returned values as distinct constructs then
       | obfuscating them with Result means you end up fighting the
       | language which becomes apparent fairly quickly. It's also a
       | frustrating experience to want to interact with returned values
       | directly and constantly have to deal with a polymorphic wrapper.
        
         | mpalmer wrote:
         | Result violates the single responsibility principle and tries
         | to make what are distinct paths into a single thing.
         | 
         | You're trying to equate code paths (outcome handling) with
         | values (outcomes), and that seems a more unfortunate category
         | error.
        
         | vjerancrnjak wrote:
         | I don't see how try-catch promotes single responsibility
         | principle. I feel like this principle is just arbitrary.
         | 
         | If I have Result<Error, Value> and I change the Error, I have
         | to change all places that are using the Error type and tweak
         | the error handling in mapLeft or flatMapLeft.
         | 
         | If I instead raise Error and change it, I have to look at all
         | the places where this error explodes and deal with it, not to
         | mention, most languages won't even give me a compile time
         | warning if I still keep the previous error type.
         | 
         | I agree that if language does not have do-notation, that it's a
         | bit ugly to sprinkle map and flatMap everywhere. Good example
         | of ugliness is https://github.com/repeale/fp-go
         | 
         | I think only an effect system, or a big environment object,
         | places everything at 1 place, and when types change you have 1
         | place to edit the code. But starting immediately with an effect
         | system (to abstract away control flow) or big env (to lift all
         | ifs up) is premature.
        
         | geon wrote:
         | There are cases when you need to store the result of a function
         | regardless of if it succeeded or threw. Like if you need to
         | tell the user that an error occurred. In those situations, a
         | `Result` can make a lot of sense.
         | 
         | Arguably, `Result` can also help when it is important that
         | error are dealt with and not just allowed to bubble up.
        
         | MangoToupe wrote:
         | > Result violates the single responsibility principle and tries
         | to make what are distinct paths into a single thing.
         | 
         | Only in languages that struggle to represent multiple shapes of
         | values with a single type. I don't think I ever want to use a
         | language with exceptions again.
        
           | solomonb wrote:
           | What language do you use which does not have exceptions?
        
             | MangoToupe wrote:
             | C, Rust, Go. With a little effort, Scala can be used this
             | way quite naturally, simply by allowing exceptions to crash
             | the process.
        
         | wsc981 wrote:
         | I'm also not a fan. Due to your point regarding code branches,
         | but also because I just don't find the code very readable.
         | 
         | I think Result<T> has its use, but I don't think this is a
         | particular great example.
        
       | tyleo wrote:
       | I always think more languages should support Result... but only
       | to handle expected error states. For example, you may expect that
       | some functions may time out or that a user may attempt an action
       | with an invalid configuration (e.g., malformed JSON).
       | 
       | Exceptions should be reserved for developer errors like edge
       | cases that haven't been considered or invalid bounds which
       | mistakenly went unchecked.
        
         | embedding-shape wrote:
         | I usually divide things in "errors" (which are really
         | "invariant violations") and "exceptions". "exceptions", as the
         | name implies, are exceptional, few and when they happen,
         | they're usually my fault, "errors" on the other hand, depending
         | on the user, happens a lot and usually surfaced to the user.
        
           | tcfhgj wrote:
           | why not divide things into errors and bugs (programming
           | errors)?
        
             | sanex wrote:
             | Not all exceptional circumstances are bugs
        
             | embedding-shape wrote:
             | I'm currently working on something that requires a GPU with
             | CUDA at runtime. If something went wrong while initializing
             | the GPU, then that'd be an exceptuion/bug/"programming
             | error" most likely. If the user somehow ended up sending
             | data to the GPU that isn't compatible/couldn't be converted
             | or whatever, then that'd be an user error, they could
             | probably fix that themselves.
             | 
             | But then for example if there is no GPU at all on the
             | system, it's neither a "programming error" nor something
             | the user could really do something about, but it is
             | exceptional, and requires us to stop and not continue.
        
               | tyleo wrote:
               | That's interesting. I'd actually consider this a user
               | error because it's only in the user's power to fix it.
               | 
               | For example:
               | 
               | 1. You'd want to display a message that they need a GPU.
               | 
               | 2. Call stack information isn't helpful in diagnosing the
               | issue.
        
               | 9rx wrote:
               | _> If something went wrong while initializing the GPU,
               | then that 'd be an exceptuion/bug/"programming error"
               | most likely._
               | 
               | That depends if it is due to the programmer making a
               | mistake in the code or an environmental condition (e.g.
               | failing hardware). The former is exceptional if detected,
               | a bug if not detected (i.e. the program errantly carries
               | on as if nothing happened, much the dismay of the user),
               | while the latter is a regular error.
               | 
               |  _> But then for example if there is no GPU at all on the
               | system, it 's neither a "programming error" nor something
               | the user could really do something about, but it is
               | exceptional_
               | 
               | Not having a GPU isn't exceptional in any sense of the
               | word. It is very much an expected condition. Normally the
               | programmer will probe the system to detect if there is
               | one and if there isn't, fall back to some other option
               | (e.g. CPU processing or, at very least, gracefully
               | exiting with feedback on how to resolve).
               | 
               | The programmer failing to do that is exceptional, though.
               | Exceptions are "runtime compiler errors". A theoretical
               | compiler could detect that you forgot to check for the
               | presence of a GPU before your program is ever run.
               | 
               | The grey area is malfunctioning CPU/memory. That isn't
               | programmer error, but we also don't have a good way to
               | think about it as a regular error either. This is what
               | "bug" was originally intended to refer to, but that usage
               | moved on long ago and there is seemingly no replacement.
        
             | kubanczyk wrote:
             | That's subtly different. It's secondary whose fault is
             | this, what primarily matters is whether you should continue
             | with the rest of the process.
             | 
             | There is always a cleanup layer, the trick is to choose
             | well between 1 and 2:                 1. Some code in the
             | same OS process is able to bring data back to order.
             | 2. OS can kill the process and thus kill any corruption
             | that was in its address space.            3. Hardware
             | on/off button can kill the entire RAM content and thus kill
             | any corruption that spilled over it.
             | 
             | Take for example an unexpected divide by zero, that's a
             | classic invariant violation. The entire process _should
             | blow up right there_ because:
             | 
             | - it knows that the data in memory is currently corrupted,
             | 
             | - it has no code to gently handle the corruption,
             | 
             | - and it knows the worst scenario that can happen: some
             | "graceful stop", etc., routine _might_ decide to save the
             | corrupted data to disk /database/third-party. Unrecoverable
             | panic (uncatchable exception) is a very good generic idea,
             | because persistently-corrupted-data bug is a hundred times
             | worse than any died-with-ugly-message bug as far as users
             | are concerned.
        
             | 9rx wrote:
             | Both bugs and exceptions can be reasonably thought of as
             | programmer error, but they are not the same kind of
             | programmer error. Exceptions are flaws in the code --
             | conditions that could have been caught at compile time with
             | a sufficiently advanced language/compiler. Whereas bugs are
             | conditions that are programatically sound, but violate
             | human expectations.
        
               | ninetyninenine wrote:
               | A little nuance: bugs are not just conditions that are
               | programmatically sound. They can encompass exceptions.
               | 
               | If a bug triggers an exception then with a strong
               | compiler that is sufficiently advanced then these bugs
               | can be found by the compiler.
        
         | teraflop wrote:
         | I find it kind of funny that this is _almost exactly_ how Java
         | 's much-maligned "checked exceptions" work. Everything old is
         | new again.
         | 
         | In Java, when you declare a function that returns type T but
         | might also throw exceptions of type A or B, the language treats
         | it as though the function returned a Result<T, A|B>. And it
         | forces the caller to either handle all possible cases, or
         | declare that you're rethrowing the exception, in which case the
         | behavior is the same as Rust's ? operator. (Except better,
         | because you get stack traces for free.)
        
           | tyleo wrote:
           | You know, I've also found this funny.
           | 
           | I like the declaration side. I think part of where it misses
           | the mark is the syntax on the caller side.
           | 
           | I feel like standard conditionals are enough to handle user
           | errors while the heavy machinery of try-catch feels
           | appropriately reserved for unexpected errors.
        
             | lock1 wrote:
             | Probably, the problem with Java's `try-catch` is it's not
             | composable and has an abundance of unchecked exceptions
             | (could mess up `catch` type). In Rust, you could just `?`
             | to short-circuit return or do another chained method call
             | `result.map(...).and_then(...).unwrap_or(...)`.
             | 
             | And more importantly, I don't think there's any JEP trying
             | to improve checked exception handling.
        
           | cies wrote:
           | Only it is not considered by the type checker. Result brings
           | errors into the realm of properly typed code that you can
           | reason about. Checked exceptions are a bad idea that did not
           | work out (makes writing functional code tedious, messes with
           | control flow, exceptions are not in the type system).
        
             | Borealid wrote:
             | The only difference between a `fun doThing: Result<X,
             | SomeError>` and a `fun doThing: X throws SomeError` is that
             | with the checked exception, unpacking of the result is
             | mandatory.
             | 
             | You're still free to wrap the X or SomeError into a tuple
             | after you get one or other other. There is no loss of type
             | specificity. It is no harder to "write functional code" -
             | anything that would go in the left() gets chained off the
             | function call result, and anything that would go in the
             | right() goes into the appropriate catch block.
        
               | jonhohle wrote:
               | I also don't understand the argument that Result is
               | anything other than a syntactic difference between these
               | ideas.                   final Foo x;         try {
               | x = foo().bar().baz().car();         } catch (Exception
               | e) {             x = null;         }         return
               | Optional.of(x);
               | 
               | vs.                   let x =
               | foo()?.bar()?.baz()?.car()?;         Some(x)
               | 
               | Both eat the error. Both wrap the value. The rust is more
               | terse, but the meaning is the same.
        
               | pornel wrote:
               | You've discarded the error type, which trivialised the
               | example. Rust's error propagation keeps the error value
               | (or converts it to the target type).
               | 
               | The difference is that Result is a value, which can be
               | stored and operated on _like any other value_. Exceptions
               | aren 't, and need to be propagated separately. This is
               | more apparent in generic code, which can work with Result
               | without knowing it's a Result. For example, if you have a
               | helper that calls a callback in parallel on every element
               | of an array, the callback can return Result, and the
               | parallel executor doesn't need to care (and returns you
               | an array of results, which you can inspect however you
               | want). OTOH with exceptions, the executor would need to
               | catch the exception and store it somehow in the returned
               | array.
        
           | pjmlp wrote:
           | While Java gets the blame, the concept was already present in
           | CLU, Modula-3 and C++ before Java was even an idea.
           | 
           | I also find a certain irony that forced checked results are
           | exactly the same idea from CS type theory point of view, even
           | if the implementation path is a different one.
        
           | CodesInChaos wrote:
           | Java's distinction between Runtime and Checked Exception
           | makes sense, and is pretty much the same panic vs Result
           | distinction Rust makes. But Java's execution of the concept
           | is terrible.
           | 
           | 1. Checked exception don't integrate well with the type-
           | system (especially generics) and functional programming. It's
           | also incompatible with creating convenient helper functions,
           | like Rust offers on Result.
           | 
           | 2. Converting checked exceptions into runtime exception is
           | extremely verbose, because Java made the assumption that the
           | type of error distinguishes between these cases. While in
           | reality errors usually start as expected in low-level
           | functions, but become unexpected at a higher level. In Rust
           | that's a simple `unwrap`/`expect`. Similarly converting a low
           | level error type to a higher level error type is a simple
           | `map_err`.
           | 
           | 3. Propagation of checked exception is implicit, unlike `?`
           | in Rust
           | 
           | Though Rust's implementation does have its weaknesses as
           | well. I'd love the ability to use `Result<T, A | B>` instead
           | of needing to define a new enum type.
        
           | dwohnitmok wrote:
           | Java's checked exceptions experiment was very painful in
           | various ways that directly exposing an error state as part of
           | the return value is not so I wouldn't quite characterize this
           | as "Everything old is new again."
           | 
           | The first big thing is that Java, especially in the days of
           | when checked exceptions were a really big thing and less so
           | in modern Java, was really into a certain kind of inheritance
           | and interface design that didn't play well with error states
           | and focused on the happy path. It is very difficult to make
           | Java-esque interfaces that play well with checked exceptions
           | because they like to abstract across network calls, in-memory
           | structures, filesystem operations, and other side effectful
           | tasks that have very different exception structures. An
           | interface might have a single `writeData` method that might
           | be backed by alternatively a write into an in-memory
           | dictionary, a filesystem key-value store, a stateless REST
           | API, or a bidirectional WebSocket channel which all have
           | wildly different exceptions that can occur.
           | 
           | The second thing is that because checked exceptions were not
           | actual return values but rather had their own special
           | channel, they often did not play well with other Java API
           | decisions such as e.g. streams or anything with `Runnable`
           | that involved essentially the equivalent of a higher-order
           | function (a function that takes as an argument another
           | function). If e.g. you had something you wanted to call in a
           | `Stream.map` that threw a checked exception, you couldn't use
           | it, even if you notated in the enclosing method that you were
           | throwing a checked exception because there was no way of
           | telling `Stream.map` "if the function being `map`ed throws an
           | exception rethrow it" which arose because checked exceptions
           | weren't actual return values and therefore couldn't be
           | manipulated the same way. You could get around it, but would
           | have to resort to some shenanigans that would need to be
           | repeated every time this issue came up for another API.
           | 
           | On the other hand if this wasn't a checked exception but was
           | directly a part o the return value of a function, it would be
           | trivial to handle this through the usual generics that Java
           | has. And that is what something like `Result` accomplishes.
        
             | jonhohle wrote:
             | IMHO the mapping issue comes from functions not being first
             | class, so all types require Functor-like interfaces which
             | are needlessly verbose. Splitting these is not semantically
             | different than a function that returns a value vs a
             | function that returns a Result.
             | 
             | I have little love for Java, but explicitly typed checked
             | exceptions are something I miss frequently in other
             | languages.
        
         | spacechild1 wrote:
         | > Exceptions should be reserved for developer errors like edge
         | cases that haven't been considered or invalid bounds which
         | mistakenly went unchecked.
         | 
         | Isn't this what assertions are for? How would a user even know
         | what exceptions they are supposed to catch?
         | 
         | IMO exceptions are for errors that the caller can handle in a
         | meaningful way. Random programmer errors are not that.
         | 
         | In practice, exceptions are not very different from Result
         | types, they are just a different style of programming. For
         | example, C++ got std::expected because many people either can't
         | or don't want to use exceptions; the use case, however, is
         | pretty much the same.
        
           | tyleo wrote:
           | I've often seen assertions throw exceptions when violated.
           | Users don't catch exceptions, developers do. Users interact
           | with the software through things like failure pop ups. You'd
           | need to check that there's a failure to show one, hence
           | returning a Result to represent the success/fail state.
        
       | DarkNova6 wrote:
       | This is really just a syntactical issue. Not one of types or
       | semantics.
       | 
       | Non trivial operations have errors when the happy path fails. And
       | with web apps IO can fail anytime, anywhere for any reasons.
       | 
       | Sometimes you want to handle them locally, sometimes globally.
       | The question is how ergonomic it is to handle this all for a
       | variety of use cases.
       | 
       | We keep reinventing the wheel because we insist that our own use
       | cases are "special" and "unique", but they really aren't.
       | 
       | Personally, I think Java's proposal on catching errors in
       | switches, next to ordinary data is the right step forward.
       | 
       | Monads are great. You can do lots of great things in them, but
       | ergonomic they are not. We should avoid polluting our type
       | systems where possible.
        
         | pornel wrote:
         | For Turing Complete languages everything is just a syntactical
         | issue (Turing Tarpit).
        
           | DarkNova6 wrote:
           | And syntax is what most programmers will complain about. Even
           | if it makes the wrong code easier to type.
        
         | DarkNova6 wrote:
         | Ah yes, -2. Predictable result on this emotional topic.
        
       | Thorrez wrote:
       | fun register(registrationRequest: UserRegistrationRequest):
       | UserDTO {             return success(registrationRequest)
       | .flatMap { validRequest ->
       | throwIfExists(validRequest.email) {
       | authService.userExists(validRequest.email) }
       | }.flatMap {                     runWithSafety {
       | authService.register(registrationRequest.email,
       | registrationRequest.password) }                 }.getOrThrow()
       | }
       | 
       | There appears to be some useless code there. Why is
       | validRequest.email being passed to throwIfExists twice? Why is
       | throwIfExists implemented to return the email if the following
       | line (runWithSafety) just ignores that returned email and instead
       | gets the email from registrationRequest.email?
        
         | jweir wrote:
         | And won't the authService.register function also error if the
         | user already exists? Or will it allow double registering the
         | account?
         | 
         | There are deeper problems here that a Result type is not gonna
         | fix.
        
       | Thorrez wrote:
       | The imperative code has                   // log exception
       | 
       | which doesn't exist in the Result version.
        
       | DrakeDeaton wrote:
       | There's certainly situations where this pattern creates some
       | tricky ambiguity, but more often than not Result is quite an
       | ergonomic pattern to work with.
       | 
       | In case it's useful for anyone, here is a simple plug-in-play
       | TypeScript version:
       | 
       | ```
       | 
       | type Ok<T = void> = T extends undefined ? { ok: true; } : { ok:
       | true; val: T; };
       | 
       | type Err<E extends ResultError = ResultError> = { ok: false; err:
       | E; };
       | 
       | type Result<T = void, E = ResultError> = { ok: true; val: T; } |
       | { ok: false; err: E | ResultError; };
       | 
       | class ResultError extends Error { override name = "ResultError"
       | as const; context?: unknown; constructor (message: string,
       | context?: unknown) { super(message); this.context = context; } }
       | 
       | const ok = <T = void>(val?: T): Ok<T> => ({ ok: true, val: val, }
       | as Ok<T>);
       | 
       | const err = (errType: string, context: unknown = {}):
       | Err<ResultError> => ({ err: new ResultError(errType, context),
       | ok: false, });
       | 
       | ```
       | 
       | ```
       | 
       | const actionTaker = await op().then(ok).catch(err);
       | 
       | if (result.ok) // handle error
       | 
       | else // use result
       | 
       | ```
       | 
       | I will be forever grateful to the developer first introduced to
       | this pattern!
        
         | bmn__ wrote:
         | This doesn't pass the smell test. Whenever I've seen the Result
         | or Either type, the definition looked different than what you
         | wrote here. I doubt this composes nicely, with Folktale and fp-
         | ts I can be certain.
        
       | hotpotat wrote:
       | With regard to AI, why not throw this whole article in an .md
       | file and point CLAUDE.md to it? Codex is better at following
       | rules so maybe you'd have more luck with that. But yeah, AI won't
       | code your way by default. People expect way too much out of the
       | interns, they need direction.
        
         | ActionHank wrote:
         | This is one of the issues with LLMs in dev IMO.
         | 
         | You either have the case that tech moves on and the LLM is out
         | of date on anything new, so adoption slows or you have tech
         | slowing down because it doesn't work with LLMs so innovation
         | slows.
         | 
         | Either way, it's great if you're working on legacy in known
         | technologies, but anything new and you have issues.
         | 
         | Can I write a spec or doc or add some context MCP? Sure, but
         | these are bandaids.
        
       | tasuki wrote:
       | > At the first glance, this code looks noisier and hard to
       | understand
       | 
       | Because of your inconsistent line-breaks!
        
       | wiseowise wrote:
       | How to rewrite boring, easily understood code into abomination.
       | I'm not surprised to see Kotlin, for some reason there's a huge
       | inferiority complex in Kotlin community where you have to write
       | the most convoluted pseudo-fp code possible (not smart enough to
       | use ML or Haskell, but still want to flex on Java noobs).
       | 
       | I can't wait until they release rich errors and this nonsense
       | with reinventing checked exceptions will finally end.
        
       | thefaux wrote:
       | I am not a fan of function chaining in the style advocated in the
       | article. In my experience functional abstractions always add
       | function call indirection (that may or may not be optimized by
       | the compiler).
       | 
       | You don't need a library implementation of fold (which can be
       | used to implement map/flatmap/etc). Instead, it can be inlined as
       | a tail recursive function (trf). This is better, in my opinion,
       | because there is no function call indirection and the trf will
       | have a name which is more clear than fold, reducing the need for
       | inline comments or inference on the part of the programmer.
       | 
       | I also am not a fan of a globally shared Result class. Ideally, a
       | language has lightweight support for defining sum/union types and
       | pattern matching on them. With Result, you are limited to one
       | happy path and one error path. For many problems, there are
       | multiple successful outputs or multiple failure modes and using
       | Result forces unnecessary nesting which bloats both the code for
       | unpacking and the runtime objects.
        
         | anal_reactor wrote:
         | Functional abstractions are great for writing code. They allow
         | to nicely and concisely express ideas that otherwise take a lot
         | of boilerplate. Now, for trying to debug said code... gl hf.
        
       | taeric wrote:
       | It is always funny to see that we try and force formulas to the
       | early elementary shape that people learn. Despite the fact that
       | chemistry, biology, physics, etc. all have advanced shapes for
       | equations that do not have the same concerns.
       | 
       | Similarly, when constructing physical things, it is not uncommon
       | to have something with fewer inputs than outputs. Along with mode
       | configured transfer of input to outputs.
        
       | valcron1000 wrote:
       | } catch (exception: Exception) {           // log exception
       | throw exception         }
       | 
       | Probably the most harmful snippet ever written. Every blog post
       | about errors has something similar written, regardless of the
       | programming language. Please, don't write, suggest or even
       | pretend that this should exist.
        
         | another_twist wrote:
         | Whats the problem ? Is throwing directly better here ? Given
         | the annotations I presume this is spring so it will probably be
         | logged anyway. So maybe its unnecessary here.
        
       | another_twist wrote:
       | I have had to write a Result abstraction multiple times. Honestly
       | I think it should be part of standard java much like Optional.
        
       ___________________________________________________________________
       (page generated 2025-10-31 23:01 UTC)