As the functions can take multiple inputs, same it can give us multiple outputs including a Value (In case of success) or a Failure object (in case of failure).
Currently we handle expcetions using try catch and kotlin.Result. Which does not gives us information about what errors a funcation could throw without reading the documentation.
I think that function should be defined as something following
fun getContent(txtFile: String) : String, FileNotFound, SecurityError
Above function describes that it returns String on Success and one of the error object on failure.
use site code
fun main () {
val txt = "some.txt"
val result = getConent(txfFile)
when(result){
is Ok -> { printlin("content: $result.value") }
is Err -> {
when(result.kind) {
is FileNotFound -> { println("file not found.") }
is SecurityErr -> { println("Permission is required to read the file.") }
}
}
}
}
It seems like you want to recreate checked exceptions. So I’d first suggest looking into what those are, and the possible reasons why they were deliberately removed from Kotlin.
Combine this with sealed classes, and you can get what you want. So in this case, your function would return Either<String, GetContentError> (or Result<String, GetContentError>, which ever library you prefer), and then you create a sealed class called GetContentError, and have subclasses that represent the possible different error states.
sealed class GetContentError
class FileNotFound : GetContentError
class SecurityError : GetContentError
I might be wrong on the syntax; I forget if you extend the sealed class, or put stuff inside the sealed class as subclasses. Hopefully you get the general idea.
If you are talking about checked exceptions, they are quite different. Exceptions are a non-local control flow - a restricted form of goto if you will, which is breaking the referential transparency of functions (which in turn makes them harder to reason about).
Union return types are nothing special, they can be simulated with types like Either, but as a very common use case, it would be nice to reduce the clunkiness and make their usage more stream-lined and convenient.
Yes, but using a union return type with an error code gives you only a subset of what checked exceptions do: like error codes, they too let you (effectively) return either a value of a given type or else a throwable; like error codes, they too force you to deal with or at least acknowledge the possibility of an error; and like error codes, they clearly document that possibility.
The mechanics and syntax are of course very different, and as you say exceptions have the option of being handled elsewhere — but you must at least acknowledge them (by catching, or by declaring as thrown by the enclosing function).
And since checked exceptions are now recognised by many as having various undesirable properties (as per my link), won’t error codes also be equally undesirable? Or if not, it’s necessary to explain why not.
What do error codes give you that checked exceptions don’t? And do they avoid those pitfalls? (And if so, how?) Without good answers for those, it’s hard to see any benefit.
(Of course, that’s not an argument against supporting union types generally — but that’s not really what the question was about.)
Using something like Arrow, where a function returns a monad, allows for error handling in a functional way, chaining map functions and stuff together, rather than having multiple try catch blocks. If you look up Railway Programming, you’ll see how it can be used pretty well.
It’s definitely still similar to checked exceptions, but allows for nicer code, imo.
Hmm, if I understand you correctly, you mean the case where e.g. we have two operations, second uses a result of the first one, we would like to call the second only if the first succeeded and if any of them fails, we need to get their error. So something like this (I don’t know Arrow):
oper1().map { oper2(it) }
But isn’t it the same as simply oper2(oper1()) if using checked exceptions?
I suppose Arrow could help if we need to map exceptions to other types or replace them with default values.
edit:
I must admit I was always confused about this: “Checked exceptions are evil, use eithers instead!” revolution. It feels like checked exceptions, only less powerful, more error-prone and without a support from the language. But I never had a chance to spend some time with it and to learn I’m entirely wrong.
Personally, I much prefer the look of the second one. But yes, ultimately the behaviour of the code is the same. I think it’s just that monads are functional, and functional programming is the ultimate programming and any other programming is wrong and bad and nasty and only for dirty peasants.
EDIT: I think you could basically compare it to Optionals; you can replace Optionals with just doing null checks all the time, but Optionals make for a much nicer development experience.
I personally prefer the first, especially if replaced functions with extensions. Second is the same code with additional boilerplate of multiple map calls. It is similar / the same as:
Optionals are actually a very good example. For me eithers vs checked exceptions is similar to optionals vs nullable/not nullable types. Now, do you use optionals in Kotlin? Why not?
Optionals were needed to differentiate between nullable and not nullable types as Java doesn’t provide such feature. Kotlin introduced not nullable types as a language feature, so we don’t need optionals anymore.
But again, I don’t say you are wrong or something. I just personally don’t fully understand this shift, but I probably need to learn the reasoning behind it.
One bad thing about checked exceptions that a lot of people mention is the tendency for bad programmers to wrap calls in a try-catch that catches and eats all exceptions, which can be very bad and lead to nasty bugs. With union types or sealed classes you can still specifically ignore errors, but there isn’t the same blast radius that catching all exceptions has.
If we were excluding features solely because bad programmers abuse them, then exceptions wouldn’t be the only feature removed!
But this is one of the problems that would be solved by exception inference. If you called a method that threw a checked exception, you wouldn’t need to either catch it (which, as we’ve said, is often the wrong thing to do, especially if you catch all) or declare it explicitly: the language would automatically declare it for you. Like current Kotlin, you could simply ignore it. But unlike current Kotlin, upstream callers would get all the information they need to handle exceptions properly, in the right place.