i don't know if this has been discussed in the pas...
# language-proposals
m
i don't know if this has been discussed in the past, but it'd be really nice if there was an enum-style shorthand for sealed data classes. basically so you can do something like this:
Copy code
sealed data class Foo {
    First,
    Second(val x: Int),
    Third(val s: String)
}
instead of this:
Copy code
sealed class Foo {
    object First : Foo()
    data class Second(val x: Int) : Foo()
    data class Third(val s: String) : Foo()
}
it's not a huge difference but it feels unnecessarily messy to do it the current way (especially when you mix in "no argument" data classes and have to use object/class instead) , and I imagine this is a pretty common use-case for sealed classes. what do you think?
👍 3
👎 6
o
It cannot be
enum
, all enum values are singletons.
m
well yes it can't be a java enum, but it can be inner classes with a hidden constructor (so like sealed classes). or did you mean there is something conceptually in the language, or practical in the compiler that prevents the keyword from being used?
o
enum is enum 🙂 it has
values
,
valueOf
, etc, that assumes all variants are singletons. Using same word for something that is significantly different is very confusing.
m
fair enough 🙂 either way i think it'd be useful with a shorthand for algebraic data types like this
o
enum
comes from the fact that all values are
enumerable
, after all.
m
that's true - in this case you would only be able to enumerate the constructors
o
so what’s wrong with sealed classes as they are?
having to write some
:Foo()
and
data class
?
m
it's not that it's wrong, it just seems like a common use-case is unnecessarily messy
yes, and sometimes
object
if you don't have any arguments to a case
(or
class
i guess)
o
It may be wordy, but I don’t think it’s messy. It states pretty explicitly what is what
m
i guess it's a matter of taste to some extent, but especially that you have to jump between using object and data class makes the code align really poorly which doesn't help readability
and it can get quite noisy as well. here's a more practical example from an android app:
Copy code
sealed class AddEditTaskEvent {
    data class DefinitionCompleted(val title: String, val description: String) : AddEditTaskEvent()
    object CreatedSuccessfully : AddEditTaskEvent()
    data class CreationFailed(val reason: String) : AddEditTaskEvent()
    object UpdatedSuccessfully : AddEditTaskEvent()
    data class UpdateFailed(val reason: String) : AddEditTaskEvent()
}
versus
Copy code
enum data class AddEditTaskEvent {
    DefinitionCompleted(val title: String, val description: String),
    CreatedSuccessfully,
    CreationFailed(val reason: String),
    UpdatedSuccessfully,
    UpdateFailed(val reason: String)
}
o
I agree with an idea that it could look better in specific cases. I don’t think
enum
is appropriate shortcut here.
As a side note you’d probably have a kdoc before each of the variants, no? 😉
m
i'm not so attached to the name, it's really the shorthand i care about
it really depends tbh but i hear what you're saying 🙂 with functional patterns types can work quite well as documentation though, and documentation that type-checks has better longevity than comments 😉
k
(FYI: this exact concept is called
enum
in rust, which misses the JVM concept of enum entirely)
g
One more problem with this approach, that now you have choice: use nested classes or don’t, for example your code snippet can looks as:
Copy code
sealed class AddEditTaskEvent
data class DefinitionCompleted(val title: String, val description: String) : AddEditTaskEvent()
object CreatedSuccessfully : AddEditTaskEvent()
data class CreationFailed(val reason: String) : AddEditTaskEvent()
object UpdatedSuccessfully : AddEditTaskEvent()
data class UpdateFailed(val reason: String) : AddEditTaskEvent()
And used as:
Copy code
CreationFailed("reason)
instead of
Copy code
AddEditTaskEvent.CreationFailed
But with implicit sealed class you don’t have such choice.
m
well, you can always import them explicitly - this isn't different from how enums work
g
you can import explicitly, sure, but it’s all about naming. By default will be used full name, because sometimes it makes a lot of sense for naming. Like
Event.Success
vs
SuccessEvent
, you don’t want to have
Event.SuccessEvent
, I just pointed that this proposal doesn’t work well with non-nested classes
m
btw for the record swifts
enum
(or enum-with-associated-data as they refer to it) works like rust too. one really frustrating thing about their enum though is that the cases aren't subtypes of the enum type - in fact they are not even types at all and you can only access them using pattern matching.
@gildor ah, yes that's true. i don't think this would be useful for every use-case though, just the case where you are using sealed data classes as an algebraic data type. for example the equivalent in other languages would be: Swift
Copy code
enum Foo {
    case First
    case Second(Int)
    case Third(String)
}
Rust
Copy code
enum Foo {
    First,
    Second(int),
    Third(String)
}
Haskell
Copy code
data Foo = First | Second Int | Third String
sealed data classes already have the right semantics for this, it's just that i think the syntax when declaring it is a bit verbose, as i stated above
g
See, version of Haskell is exactly what I’m talking about. It’s
First
, not
Foo.First
. i don’t think this would be useful for every use-case
That’s the problem, you suggest new language feature and new syntax, which is big change itself, so should work for so many cases as it possible Maybe there are some other ways to avoid additional verbosity, but now it’s pretty limited, for example you also want to have constructor properties for parent sealed class, but this syntax doesn’t allow that
m
honestly i would like this to be more of an extension/shorthand to existing language features rather than a completely new one. isn't there already precedent where language features work as shorthand for other features but don't support every possible scenario? i'm thinking of things like interface delegation being shorthand for implementing interfaces yourself (delegation is less verbose but also a strictly less powerful way to do it)
g
class delegation is definitely a bad example of well designed feature and Kotlin team and Andrey Breslav many times mentioned this
m
fair enough, it was just the first thing that came to mind
g
but even in case of delegation it’s potentianlly removes much more boilerplate than this proposal (which is very limited)
m
but do you think the idea of having a shorthand for this is a fundamentally flawed idea for kotlin, or is it more about my specific proposal?
g
I would like to write less code for sealed classes, but I don’t have any good solutions which are flexible enough and play nicely with existing code.
and I think proposed solution is too limited to be a part of the language: enum keyword is misused, what about not nested classes, how to share properties
if use case is only for simplest cases (like Foo above), than I don’t see big advantages, it actually creates a new syntax for one particular use case of existing feature
m
if it is a common use-case i don't see why it would be a bad idea to provide shorthand? just like
data class
is shorthand for a specific kind of
class
(namely a product type), i'd really like to see something like a
sealed data class
as a complement for supporting sum types.
g
because data class saves a lot of typing
this one saves one constructor call without arguments
also has completely new semantics about classes, data classes and object
m
plus mixed `object`/`data class` prefixes
g
yes, and this is what I mean about new semantics, sorry bad word for this probably
what if you need just a class?
, i’d really like to see something like a
sealed data class
this actually interesting addition, maybe this makes more sense, because doesn’t clash with existing implementation
m
that is the concept i'm trying to describe 🙂
it was in my original message but i updated the code now to also reflect it
sorry about that
o
sealed data class
clashes with existing meaning. E.g. if you have
inner class
and you make it
inner data class
it doesn’t change syntax that radically
(again, I’m not against the idea itself, I just discard some syntactic ideas based on experience in this language design)
👍 2
m
i really appreciate the feedback!
o
Also note, that since each item is still a class, it can have a body (with functions, overrides, etc). So syntax should either accomodate for it (and make it non-ambiguous to parse), or disallow it in the short form.
m
yes, that is true
even enums allow bodies after all
g
even enums allow bodies after all
But the same body for each item, which may be too restrictive for sealed class (because each item has own types in constructor, so probably want to have own methods)
m
yes, i agree
d
So the thing he's proposing is enum-like syntax for a concept dubbed
sealed data class
. Body of the parent class can follow the
;
just like in enum class (even if I'm not personally a fan of that syntax)
The only thing that remains is how the parent constructor is called - in
enum class
you call the parent constructor instead of declaring a new one
Very interesting idea but it definitely needs to be approached critically. Come up with a design that has answers to points of critique.
m
absolutely - this kind of feedback is precisely what i was looking for when bringing it up
l
Enum cases can have different classes as each case can have a body, so they're not so different from one level hierarchy nested sealed classes.
g
Ah, yes, sure