Anyone have any favorite rules on how to remember ...
# getting-started
c
Anyone have any favorite rules on how to remember to choose enums vs sealed classes? For example, I need to Represent 3 states (pass, fail, unknown) and the fail state should also have an "int" status code. I think enums mostly get me there, but should i do this with sealed classes instead?
j
Let's start with the obvious: if you can potentially have different exit codes for the failed state, you simply cannot do it with an enum, because there would be only one instance of this failed value, with only one fixed exit code
c
Enums are fancy String constants. Sealed classes are just classes, but only have specific sub-classes
❤️ 1
1
So if you don’t have additional data to add to your result, an Enum is fine. If you need to provide additional context, you’ll need a class that can hold that value, so a Sealed Class is what you need
1
j
My usual rule of thumb is: • if all values have the same set of properties by definition/essence (not by accident), it means they are essentially of the same type. ◦ if I know all instances at compile time and it doesn't make sense to instantiate new values dynamically, I would go for an enum ◦ if not, I'd use a plain (data) class • if they need a different set of properties (thus are values of different types), I actually use different types (interfaces/class hierarchy), and if the set of subtypes happens to be known at compile time and it doesn't make sense to define new subtypes in other modules, I'd go for
sealed
hierarchies (with sealed classes and/or interfaces)
👍 1
1
c
In your example, since one of your states has an additional “int” status code, that needs to be in a sealed class. You cannot provide extra data to an Enum, since they are constant values and cannot be changed
👍 1
p
Enums are fancy String constants.
Sealed classes are just classes, but only have specific
sub-classes
🫶 , Although they technically abstract classes
👍 1
j
Enums are fancy String constants
@Casey Brooks That's a hell of a shortcut, though 😄 They actually are a fixed set of instances of a single class, with properties and methods, possibly custom equality... that's quite a bit more than just strings
🙂 1
To me an enum is closer to a regular final class + a few globally accessible variables initialized as instances of that class. The differences with that is that you can't instantiate it, and you have the ability to list all values of that type programmatically without reflection.
👍 1
c
I was trying to give a short, pithy way to remember the difference. It obviously misses a lot of nuance, but gets the main idea across in something easy to remember Enum cases are closer to String constants than classes in that they’re constant. Use them when the values never change. The confusion here is probably more accurately described by the difference between an
enum class
and a
sealed class
with
object
values. The more accurate comparison of terms is: •
enum class
is similar to a
sealed class
in that it gives you a way to enumate a fixed set of values, especially within a
when()
block • a single value (enum case) within an enum class is similar to String constant, or an
object
• An
object
within a
sealed class
is similar to an enum case. It can be part of a
when()
block • A regular
class
within a
sealed class
has no equivalent in an
enum class
. This is the thing that will help you decide when you want enums vs sealed classes
😸 1
👍 1
💯 3
w
Sealed types in 95% of the cases are like enum types in other modern languages like Swift and Rust where it only represents state (there is this cool proposal to make this easier in Kotlin, it makes it much more clear what the intended use of enums sealed types is). But on top of this Kotlin gave enums and sealed types an OOP twist, which makes it much more flexible.
👍 1
c
Yet another (equivalent) way to compare them: an
enum class
is the same as a
sealed class
which only has
object
inheritors
💡 2
p
However, technically the enum constructor can receive params not the case for an object. However, I see your analogy
c
That's just a difference in writing, it's not important:
Copy code
enum class Foo(val id: String) {
    First("first"),
    Second("second"),
    ;
}
vs.
Copy code
sealed class Foo(val id: String) {
    object First : Foo("first")
    object Second : Foo("second")
}
6
c
@Casey Brooks thanks for that. I think that's what I needed to make things click.
thanks everyone. i definitely got more clarity from reading this. it seems like all of the articles i found were missing the point and too verbose. thanks for teaching everyone
c
I personally don’t use enums anymore, as they only work to the point at which you need the capabilities of a sealed class hierarchy and have to refactor to that anyhow. Find it cleaner to consistently use sealed classes (even when an enum would also work).
🔥 1
1
c
Yeah. thats kinda where im at. considering just ditching enums totally. lol
K 2
c
yep. related is the old-school Java-naming of ENUM_VALUES. Enums (and sealed types) are Types, and should follow the naming convention the same as other Types.
🔥 2
While solely a naming convention difference it seems to arbitrarily magnify the delta between enums and sealed types.
🔥 2
j
Similar to interface vs class. Enums can’t have state, classes can.
c
not quite the same. Enums can have state - but its constant, set at creation time. Example from above with state:
Copy code
enum class Foo(val id: String) {
    First("first"),
    Second("second"),
    ;
}
enums are objects (instances). sealed classes can be objects or classes (where you can create new instances thereof with specific state).
j
Mmm I am thinking about state from an Android perspective, which means anything that can change over the time, so a constant couldn’t be a state.
c
no idea on that Android stuff 😉. You can setup both sealed classes and enums to have mutable state (vars). Not usually appropriate for singleton objects, but possible.
j
Was that always allowed in enums? I thought it was not possible to use
var
with enums
c
Copy code
enum class Foo(var x : String) {
    One("a"),
    Two("b")
}
In most cases this a Very Bad Thing to do - but it is possible.
j
Looks like it was added in Kotlin 1.2, I thought it wasn't a thing yet
e
Late to the party but I want to stress that library maintainers should always go sealed interface first (not even sealed class!). eg. this,
Copy code
sealed interface Trim {
  companion object {
    val Start: Trim = TrimImpl(...)
    val End: Trim = TrimImpl(...)
    val Both: Trim = TrimImpl(...)
  }
}
will evolve easier than enums & also easier than the sealed interface that exposes subclasses to public API. (advice brought to you by someone who has seen pain 😅 maintaining binary compatibility of public library APIs)
👍 1
c
ooh. wonder what sealed classes vs interfaces give me. lol
e
No need to maintain consructor method binary compat, if you need constructors just do static top-level function with same name as interface. Would save you trouble for instance if you needed to “construct” a subclass by default in the future, you can just change the function impl to return a subclass, cant do that with a constructor of a class (personal opinion however).