

Join the discussion
Write your take first — we'll ask for email only when you're ready to publish.
- Hacker News
- 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.
by time4tea - In Kotlin, my go-to library is https://arrow-kt.io/by LelouBil
- 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.
by RedNifre - Smells like something that Effect-TS is designed to solve in the TypeScript world.by tyteen4a03
- For the typescript world, there is neverthrow[0] which offers a similar Result type.by paduc
everywhere orPromise<Result<number, string>>
and thentype EitherT[F[_], E, A] = F[Either[E, A]]
?def dosomething(): F[String, Number]Isn't this beautiful: https://github.com/7mind/distage-example/blob/develop/bifunc... ?
by pshirshov- 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):
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.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(); }by flaie - 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!
by DrakeDeaton - 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.by bmn__
- 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.
by thefaux - 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.by anal_reactor
- 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.
by topspin
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.} catch (exception: Exception) { // log exception throw exception }by valcron1000- 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.
- 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.
- 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.by rockyj
- > 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`)
by rileymichael
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?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() }by Thorrez- 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.
by jweir - 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.by iandanforth
- 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.
by wsc981 - 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.
by geon - > 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.
by MangoToupe
You're trying to equate code paths (outcome handling) with values (outcomes), and that seems a more unfortunate category error.Result violates the single responsibility principle and tries to make what are distinct paths into a single thing.by mpalmer- 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.
by vjerancrnjak - 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.
by tyleo - > 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.
by spacechild1 - 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.
- 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.)
by teraflop