<https://kt.academy/article/union-types-into>
# feed
m
c
There are many names for more-less the same thing: sum types, union types, tagged union types, variants, etc. They are a VERY good idea for a static type system, and a lot of the problems we have in popular languages can be solves with them... The problem is: we did not have them for so long that the stdlibs and library ecosystems do not use them, and subsequently developers are unfamiliar with them. Kotlin allows something that works RIGHT NOW by using sealed classes! Here one example from our code:
Copy code
sealed class GuestListResult<T> {

  enum class GuestListErrorType {
    GuestAlreadyCheckedIn,
    GuestListExceedingCapacity
  }

  data class GuestListSuccess<T>(val data: T) : GuestListResult<T>()
  data class GuestListError<T>(val errorType: GuestListErrorType, val errorMessage: String) : GuestListResult<T>()

  // ...
}
This models the reality very closely. In case of success there is no error (and thus no error type and no error message) and vice versa: when there's an error there is not success data of type T. This how it's done in Haskell and Elm: there it works really well. Having "ad hoc" union types could work too of course. And I'm not so deep into compile design that I know why working with sum types is so hard to optimize. But saying that the second type in in the union (after
|
) is the error, and the syntactical sugar for it (with
!.
and
!:
) is a little too much for me. I prefer generally re-usable syntax, that does not force me to only use this for errors.
šŸ‘ 4
šŸ‘šŸ¾ 1
m
I feel in general that that is a lot like a built-in support for
Result
. To me, the relation between union types with errors and
Result
is the same as between nullable types and
Optional
. It is a good argument that libraries and our practices are not adjusted, but this is backwards-compatible change, and consistent with Kotlin philosophy in general - unchecked (unexpected) exceptions should be thrown, checked (expected) should be expressed in result type. There only remains decision how we express them: with union type, with
Result
, or with
Either
?
c
I think this topic is waaaaay bigger than success-or-error types. We have recently started using
RemoteData
in our Elm projects. It's similar to
Result
but has two additional variants: not-asked and loading. Using this greatly cleaned up our code and make it more hackable and defensive. http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipattern.html What I argue it that we need proper sum types. We need them as much as we need product types. We're just not accustomed to using them as they are not easily available in most popular languages and datastores.
āž• 1
šŸ• 1
m
Well, then you need sealed class :p
l
We have union types in PHP. They’re quite useful when used correctly, mostly in cases where you want to allow different variants of the same type that can be isomorphic. Eg, accept an object or a map and convert the map into the object if necessary. Especially helpful when changing an API, too, as you can use the union as a transition period. I’ve also used it to emulate Result types, which are hard in PHP without generics. Like many things, union types are a surgical tool, useful in very specific places.
It looks like the new feature (from the article above) looks like how I’ve used them in PHP, although with additional operator support that I’d love. šŸ™‚ I’m unclear how it’s restricted to errors, though… Are unions only allowed if one of the items is an instance of Error? That’s not entirely clear.
m
I should add a section that explains that. This is an early design, the current state of this design enforces error class to have special new modifier.
l
Yeah, I had to go back and look again. So you can only union on returns, and only with an object that implements some interface, or similar marker?
I’ve been talking about that for PHP, so I’m in favor conceptually. But in all honesty, I’d rather have a better native Result type that doesn’t force you to create an exception. (Arrow’s Either is nice, but not as nice as Rust’s Result.)
c
"I’d rather have a better native Result type that doesn’t force you to create an exception. (Arrow’s Either is nice, but not as nice as Rust’s Result.)" -- I totally agree with this. "Are unions only allowed if one of the items is an instance of Error?" -- no, but is you have syntax like
!.
and
!:
then it kind of is.
l
OK, so that’s very similar to what I’m doing in PHP now. We just don’t have the extra syntax.
m
@Cies if you don’t want to express an exception use nullable type or you can define some generic exception and define function to help you create Result.failure with this exception.
šŸ‘Ž 1
l
I’m either using Arrow’s Either, or in one case I rolled my own 3-state result type. šŸ™‚
a
@marcinmoskala. There is definitely a cost to using exceptions as control flow. I also believe exceptions should really be a last resort. Take a look at the resource on top of this page: https://arrow-kt.io/learn/typed-errors/working-with-typed-errors/
c
We are also against using Exceptions except in last resort. If they are thrown in 3rd party code: try to catch them as much as possible. Only exceptions that are irrecoverable should bubble up
m
I've written a devil's advocacy of exceptions some years ago: https://blog.plan99.net/what-s-wrong-with-exceptions-nothing-cee2ed0616
āž• 1
c
@mikehearn I read your blog, while i hate exception-overuse. i learned about the perf impact of not having exceptions. the rest of the arguments i find a bit flimsy. even most people arguing against overuse of exceptions understand that some are needed (ok, except camp Haskell and Zig make good cases against it). i think a lot is in the word "exception": they need to be reserved for truly exceptional situation (like: a file being (re)moved while reading from it; not like: a number that cannot be parsed from a string).
l
I pass this article around a ton. It’s quite long, bit the best discussion of error handling I’ve ever read. https://joeduffyblog.com/2016/02/07/the-error-model/
m
@Cies Yes, sometimes the issue is the implementor of a function can't know what is considered exceptional or not. A data source may be expected to never have unparseable numbers (exceptional) or to have them frequently, and the same parsing code is used in both cases. The way Kotlin does it by giving you the option at call time is quite nice, and usually good enough.
s
@Cies maybe you like this Result type for Kotlin!? https://github.com/michaelbull/kotlin-result
c
@Stephan Schrƶder That's a nice Resutl type indeed!
@mikehearn I agree that it's nice to have both in some cases, but that also means that not using the "exception throwing function" becomes discipline. in Elm its simply not possible.
l
I think I considered that result type, as it looked nice, but eventually was convinced that Arrow was more standard, even if I dislike ā€œLeft errorā€ eithers.
d
Just reporting here as a happy user of kotlin-result šŸ™‚ Although I will definitely try to switch to Arrow's Raise DSL once the context parameters are standardized. Btw what's the relation between the announced union type for errors and the Raise DSL? Will the new native feature obsolete the library one?
l
I think they’ll be alternate ways of handling similar things. A union return is essentially a ā€œnaked Eitherā€, which you can use like one but it doesn’t enforce that you do so the way an Either/Result wrapper does.
šŸ‘ 1
I’ve written about this model before, in the context of PHP: https://peakd.com/hive-168588/@crell/much-ado-about-null
m
@Larry Garfield I like this term ā€žnaked Eitherā€, but I like even more ā€žnaked Resultā€
šŸ‘ 2
c
since there are operators like
!.
and
!:
I think "naked Result" is more true to what it is... (only the stdlib
Result
in Kotlin has an Exception class constraint on the error parameter, where "naked Results" do not).
l
Yeah, Result vs Either is a subtle, semantic fight. šŸ™‚ (I do not understand the point of the stdlib Result, frankly.)
Though I think my ideal would be lightweight checked exceptions a la Midori. That’s essentially isomorphic to a Result, but with more dedicated syntax.