Hey! ```data class Item (val name: String) seale...
# announcements
t
Hey!
Copy code
data class Item (val name: String)

sealed class Permission <T> {
    object Create : Permission<Unit>()
    object Edit : Permission<Item>()
}

fun <T> verify(permission: Permission<T>, item: T): Boolean = when (permission) {
    is Create -> true
    is Edit   -> item.name == "editable" // Unresolved reference: name
}
For some reason I expected that when it's known that
T
is
Item
(as it is in
Edit
), then
item
would be properly casted, but I realise that maybe I expected too much. Any idea on how to connect the two (
permission
and
item
)?
f
Maybe this:
Copy code
fun <T> verify(permission: Permission<T>, item: T): Boolean = when (permission) {
    is Permission.Create -> true
    is Permission.Edit   -> (item as Item).name == "editable" // Unresolved reference: name
}
t
Thanks for the reply! While this seems to work, the type system doesn't stop me from casting it as anything else. For example, I can cast
item
as
Item
in
Create
case. Could this be connected with type erasure? Just for completeness, the same example in Haskell typechecks:
Copy code
{-# LANGUAGE GADTs #-}

data Item = MkItem { itemName :: String }

data Permission a where
    Create :: Permission ()
    Edit :: Permission Item

verify :: Permission a -> a -> Bool
verify permission item = case permission of
    Create -> True
    Edit   -> itemName item == "test"
c
type erasure is a thing, but that's not what's happening here. either there's some edge case where those arguments might not match (which I don't see right now), or the type system is just a little too dumb to recognize the type of T properly, which happens as for why you can cast to Item in the other case, that's because
as
is not a compile-time-checked cast, but a runtime-checked one. You can use it anywhere you want, but it will crash at runtime if it's not a valid cast
👍 1
j
Copy code
data class Item (val name: String)

sealed class Permission {
    object Create : Permission()
    object Edit : Permission()
}
fun verify(permission: Permission, item: Item): Boolean = when (permission) {
    Permission.Create -> true
    Permission.Edit   -> item.name == "editable"
}
Do you really need generics? Nothing inside Permission is dependent on the type T. Also, when you have object instances you don't need to use "is" since they are singletons.
👆 1
c
now you have to pass an Item for the Create case as well though
make it nullable at least
t
Yes, this is a simplified example with only 1 data class, in reality there are more permissions too. I have tried both "is ..." and without (since objects are a type and an instance at the same time). Now I realise that what I wanted to do is known as "phantom type". What I'll probably do is remove the type parameter from
Permission
and have each permission have a field with the value (so
Create
stays object,
Edit
is converted to data class).
Daniil, I planned to have an overloaded
verify
for
Permission<Unit>
that passes Unit as second argument.
s
I feel like this might be the closest to what you were initially thinking of?
Copy code
data class Item (val name: String)

sealed class Permission<T>(val value: T) {
  object Create : Permission<Unit>(Unit)
  class Edit(item: Item) : Permission<Item>(item)
}

fun verify(permission: Permission<*>): Boolean = when (permission) {
  Permission.Create -> true
  is Permission.Edit -> permission.value.name == "editable"
}
👆 2
👍 1
j
Alternatively:
Copy code
fun <T> verify(permission: Permission<out T>, item : T): Boolean = when (permission) {
    Permission.Create -> true
    Permission.Edit -> when(item) {
        is Item -> item.name == "editable"
        else -> false
    }
}
The smart cast just can't cope with the relationship between Permission<T> and Item:T, but you can just have an inner when. Notice the out modifier, which allows the following call to compile:
Copy code
verify(Permission.Create, item)
👍 1
t
Thank you all for the replies! Shawn's idea is what I will probably go with.
👍 1