[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)