So, I’m trying to make an honest effort to write c...
# announcements
p
So, I’m trying to make an honest effort to write code that doesn’t use exceptions to indicate expected (non-logic) errors, but I keep running into cases that exceptions solve very nicely and sealed classes do not. I’m finding it really hard to resist the urge to use exceptions, even though the fact that they’re unchecked means the type system doesn’t enforce error handling discipline on the caller. Examples of cases I don’t have good answers for: • Short-circuiting. I have to make a bunch of calls, the first of which that fails should stop execution of the rest. I’m not sure I see a good pattern for this with e.g. a Result type, other than wrapping each call with the same boilerplate to check the Result type and return early if appropriate. • Validation when instantiating a data class. Say I have a data class whose constructor does some validation on the passed-in values (say, one of its fields is an enum, but it’s being populated from an API call which returns a string). There’s no way that I can think of to signify failure in a constructor other than throwing an exception, since you can’t change the return type.
any suggestions for sane patterns for these cases?
n
For the second one you'd just make the actual constructor private
p
I’m not sure I see how that helps!
n
And create a free function with the same name as the class
p
oh I see.
n
That returns an expected
p
and mask the warning
n
I wouldn't use the word mask here
p
sorry, misspoke
n
But yeah once you are working with a function instead of a constructor it can return an expected and there's no issue
p
Copy code
@Suppress("FunctionName")
n
Sorry not sure I follow that
p
well, having a function with the same name as the type results (at least in intellij) in a warning
n
Ah gotcha
If you want they can be different names
p
But also --- it seems like you’re doing something a little bit astonishing if you have a “constructor” that doesn’t return the object
n
It doesn't really matter
Sure
So maybe different names is better
But the idea is the dame
Same
p
sure… but now it’s pretty awkward to use that one data class, and it doesn’t look like others nearby it that don’t need to do that validation.
n
Why is that awkward
Doesn't seem like a big deal to me
p
you have one data class that has one calling convention, and a bunch of others with a different one
n
It won't look like the other ones anyway
p
depending on whether or not it needs to be able to signal an error
n
It will return an expected and not a class instance
d
the first of which that fails should stop execution of the rest
Isn't this solved by merely returning from your current scope with the relevant sealed-class state, early? If you're within a function context where a bare
return
is unexpected, you can still return by explicitly labelling the return point.
n
But that's the whole point
p
@darkmoon_uk I called that out though --- yes, but you end up writing a bunch of boilerplate
n
You have different code based on whether or not errors are poasible
p
right… but I guess, it’s possible to have a sea of data classes where some need error handling and some do not.
that represent the schema of some api
and the safe, clean thing to do would be to assume that errors are always possible — that one particular endpoint doesn’t do any validation is something the caller doesn’t need to know about
n
Well that's basically the exception world
p
with exceptions, you can throw a ValidationException where appropriate, then catch them at the top where you’re making the request.
n
You assume that anything can throw and just let it bubble up
p
with non-exception error handling, if you realize you need to layer on validation, you now need to rewrite every place that one of these data classes is instantiated
n
The whole point here is that error handling will be more statically enforced and we're going to only indicate errors can occur where they actually can
You only need to rewrite if suddenly something that used to be error free now can cause an error
p
which feels like making the caller responsible for knowing nitty gritty details of individual objects of subclasses it otherwise wouldn’t need to know anythign about
n
Amd proponents of that style would argue it's a feature
Sorry but this is kind of going from solving specific issues with using expected to a debate of expected vs exceptions
p
well.. I guess that may be the real problem --- I’m framing the issue in a way where exceptions seem like the clear solution, but that may indicate that I’m framing it wrong
n
All these things are trade offs
p
I guess I’m just feeling like … I want to be able to write code that reads in a way that indicates to other developers what it’s doing. And I’m finding myself writing boilerplate that feels like it’s obscuring my meaning
which suggests to me that I’m going about it wrong 😕
n
I dunno in the solution for the dataclass issue I don't see how that adds boilerplate
The classes that can error will be instantiated a little differently
And that's ok
That the point
Re the first issue
I believe Arrow has some stuff to help with this
p
I’ll check it out.
n
Another option which you may be a bit amused by is simply runCatching
p
sure — but at that point, I’m throwing exceptions! 🙂
n
you're throwing them a very short distance
p
sure --- but no shorter than if I used try/catch
n
Sure
It depends what you want to do with it after
p
yeah, fair
and good callout
n
Kotlin doesn't really have a super nice solution for this like say Rusts ?
Btw the lowest boilerplate way of doing early return with sealed class type errors
Is something like val x = foo().getOrElse { return err }
p
I’d heard, and it sounds nice — I really should go play with rust
n
Something like that
Should be possible to get to work
p
so — question about that
d
@Nir That looks good for achieving an 'exception-like syntax' around error-case sealed-class returns. Maybe you could make that a function of some Error-case Interface, so that it can be more easily reused across different sealed-class hierarchies.
p
actually never mind 🙂 I was trying to think of a good way to have a hierarchy of Result types that all defined those convenience methods (getOrNull, getOrElse) (since you can’t just check if
this
is
Success
from a superclass, since every Result type will have its own
Success
)
but I think I thought of a decent way to phrase it
n
Yeah doing it all ideally is something that requires some thought
I'm rather surprised and disappointed that whenever this is brought up, the language team seems to kick it back to developers
p
I really look forward to the official Result class being something we all can use!
n
Not clear if that's even the plan for it from what I understand
p
yeah… I think that’s part of my frustration. Anyone can come up with a good pattern, but if there isn’t a standard one everyone will come up with a different one
n
Right
p
and none of them will be great
maybe a few 😆
n
It's very strange to me there Kotlin doesn't see the value in standardizing this style of error handling more
p
yeah… like…
n
But there it is
I've been in pretty long threads on this slack and seen people who know Kotlin much better than me argue this point
p
I’d love to see a standard pattern not just in the standard library but built into the language, so that there was syntactic sugar around it to guide you towards it and to make it feel natural
right now, what feels natural is returning null on error
n
Yeah, agree it needs syntactic sugar
p
which works sometimes but not always!
n
Null isn't really good enough though
Hardly ever
p
it is very occasionally --- “this wasn’t in the map”
but yeah --- I feel myself pushed towards null and it makes me uncomfortalbe
I want to communicate more to the caller
n
Well, that's not really an error per se right, just a specific not unexpected state
But even converting string to integer, do you think null is enough?
p
well sure, but exceptions don’t necessarily indicate error — just the exceptional case
a good pattern for “the exceptional case” would be nice!
n
What if I want to distinguish between "can't parse because your string is nonsense" and "can't parse because your string is an integer that is too big"
p
@Nir I’d much rather have the language say, “hey, cut that out” in that case 😆
yeah exactly.
n
So null isn't really enough even in very simple cases
p
so most of the time, you really need to build something
which, sealed classes are a great component of
but aren’t the whole something.
n
Even with the map it's not great, what if you have nullable values ;-)
Then get becomes useless
p
This is in the context of a codebase I’m coming into, that has its own rudimentary Result type which it uses occasionally, but the Error value is just a string
n
But this is a separate issue with null
p
which isn’t good enouhg for my use case. But Solving error handling for the whole codebase feels wildly out of scope for the current task (though very urgent)
n
Yeah, I would probably have a generic approach where you could stuff whatever error type you wanted
p
and meanwhile, exceptions are sitting over here, saying, “heeeey”
yeah, I started fiddling with something tonight, but I think I’m gonna leave that on a branch for now 😆
n
Yeah that's what some people are claiming is already happening
No default solution so people just turn to exceptions because it's easiest
p
yeah…. I noticed ktor uses exceptions in the ‘wrong’ way (and turned it off)
but what I haven’t seen much of, in any library I’ve used so far, is library authors returning Results
n
You're not really supposed to use the standard library result
p
well, but I mean, or anything like them.
n
And I'm not sure that any one other one has caught on enough
p
I’m realizing that my brain is pretty fried for the night, and I think it’s probably time to sign off
this was really helpful though 🙂
thanks for the conversation!
have a good night. 🙂
👍 1