I'm having massive, massive problems working with ...
# getting-started
c
I'm having massive, massive problems working with Generic Types in Kotlin and I'm not finding a lot of resources to help clarify what's going on. I'm not sure if the type system is just too restrictive to express my architecture or what. It got bad enough that I actually tore all the typing out in favor of just casting everywhere, but that makes the code incredibly ugly and frustrating to work with as well.
I have a base class,
Puzzle
. A
Puzzle
contains a
PuzzleState
. A
PuzzleState
contains a
Selection
and a
Guess
. All of these are represented by subclasses, so a
Crossword
might inherent from
Puzzle
, with a
CrosswordState
that consists of a
GridSelection
and a
GridGuess
.
First problem: if I define a CrosswordState as
class CrosswordState : PuzzleState<GridSelection, GridGuess>()
I can't actually pass it into anything expecting a
PuzzleState
.
s
Just because
B
is a subtype of
A
, that doesn't necessarily mean
Foo<B>
is a subtype of
Foo<A>
. Sounds like you might need to add some type variance.
☝️ 1
☝🏻 1
Hard to say more without an example of the code
c
Is there any way to nest generics? Like, if I have a
Puzzle<T: PuzzleState<Any>
can I define a method on
Puzzle
which returns the specific
Any
type on the
PuzzleState
?
s
like this?
Copy code
class Puzzle<S, T: PuzzleState<S>> {
  val state: T
  fun getStateSomething(): S = state.something
}
c
Well, but then you're always declaring
Puzzle
as
Puzzle<String, PuzzleState<String>>
, right?
Not
PuzzleState<Puzzle<String>>
s
It's possible you could simplify to
Copy code
class Puzzle<S> {
  val state: PuzzleState<S>
  fun getStateSomething(): S = state.something
}
but sometimes you do end up with a lot of repetition in generic types, it's true
c
The problem with simplifying like that is now in order to use these things you have to know the internal details of how they operate.
Puzzles have Puzzle states, and PuzzleStates have Guesses, and those Guesses are mostly mappings of one type to another. So you end up specifying the details of the Guesses in your definition of the Puzzle.
And then if you have a generic PuzzleManager you end up repeating those types in there, all the way through the hierarchy.
s
You only need to specify them if you actually intend to use them. Otherwise, you can just use wildcards (
*
).
1
c
Well, I do use them in some places. But I don't need or want to know the types. If
PuzzleState<T>
has a
fun getData() : T
method, I'd like to be able to define a
getData()
method on the
Puzzle
which returns a
String
or an
Int
or whatever based on the type on the
PuzzleState
It sounds like there's no way of doing that.
j
If someone using your
Puzzle
needs to get a specific type via
Puzzle.getData
, then you actually need
Puzzle
to know about that specific type. This "requirement" of yours is what creates the need for
Puzzle
to know about nested stuff
You might not need to use the concrete
PuzzleState
type anywhere, in which case you might just need
Puzzle
to know about the guess and the selection type:
Copy code
open class Puzzle<S, G> {
    protected val state: PuzzleState<S, G>
}
c
I can make the class sealed. Then all that information would be available at compile time. But there's no way to point to it in the class definition.
d
I've gone down this rabbit-hole myself a few times, and it usually means I've got my design wrong and I'm making things more complicated than necessary. What generic functionality do you hope to gain by having Puzzle and PuzzleState?
c
PuzzleState is needed to persist data, separate from the UI
Puzzle determines the screen and layout and interaction of the app.
d
Okay, but what specific methods are on PuzzleState that need to be generic?
c
Well, puzzles need to be able to check if they've been solved, and record input, and save and load their state from persistent stores and cycle it in and out of ViewModels. It's a lot of boilerplate.
I was trying to abstract that machinery into one place.
d
Copy code
interface Puzzle {
    val state: PuzzleState
}

interface PuzzleState


class CrosswordPuzzle : Puzzle {
    override val state: CrosswordPuzzleState = CrosswordPuzzleState()
}

class CrosswordPuzzleState : PuzzleState
No generics required here.
c
No. And that's what I've ended up back at.
But the state is a
var
So it gets ugly because it has to be cast everywhere. Or I create helper methods.
d
Hmm, you can make it a
var
in CrosswordPuzzle, but Puzzle should only have it a val.
What writes to PuzzleState?
c
It's where I store my UI state.
So I mostly get them from ViewModels
d
Are the ViewModels specific to a type of puzzle?
c
No.
d
Then how do they know how to update the puzzle state?
c
PuzzleState isn't editable. There's an
update
method on the ViewModels that replaces them.
d
How does the ViewModel know what to replace it with?
c
Most of my draw methods take ViewModels, and they create proxies to the PuzzleState. When the user makes changes I call update on the ViewModel with the new PuzzleState.
d
What creates the new PuzzleState?
c
Callbacks in the UI mostly.
d
And those are specific to the PuzzleType?
c
Usually. Like, in a crossword puzzle you could select a square in the grid or in the clue list. In a jigsaw, you select specific jigsaw puzzle pieces.
d
What I'm trying to get at is that at some point you have the actual Puzzle and PuzzleType states that you know about.
c
So the selection class in each of those is different.
d
Maybe you need some sort of controller specific to the puzzle type that handles all of the coordination of puzzle-specific objects/types.
c
Maybe.
d
What I like to do for this sort of thing is work out the interfaces that are specific for the domain(s) I'm trying to model. The assumption is that the state for the domain would be fully managed from these APIs, and not exposed externally.
so, puzzleState isn't a public property.
Instead, the ViewModels would get "command" objects (eg,
()->Unit
lambdas) that call methods directly on the known puzzle type. the subtype of puzzle would have methods that update the state depending on the action.
Something more like this:
Copy code
fun interface Choice {
    fun chosen()
}

interface Puzzle {
    val availableChoices: List<Choice>
}

class CrosswordPuzzle : Puzzle {
    private var state: CrosswordPuzzleState = CrosswordPuzzleState("initial state")

    override val availableChoices: List<Choice>
        get() = listOf(
            Choice { updateState("user chose first guess") },
            Choice { updateState("user chose second guess") },
        )

    private fun updateState(newState: String) {
        state = CrosswordPuzzleState(newState)
    }
}

class CrosswordPuzzleState(val state: String)
c
Maybe that's a better way to go. I need to think about it a little.
d
It's your project, so it's up to you 😉 I'm just thinking about how I'd go about it.
c
I appreciate it.