So I have some code for a command-line based flash card memorization app, and the pattern I came up with before discovering coroutines is very reminiscent of how a suspend function would look after compiler transformation (assuming every “expect” call were suspending instead of blocking):
private fun doGuessingGameWithOptionsLoop(context: Context, flashCardSetFiles: Array<File>) {
val out = context.out
var flashCardSet: YamlFlashCardSet? = null
var tables: Array<YamlTable>? = null
var guessColumnIndices: IntArray? = null
var displayColumnIndices: IntArray? = null
var rowChoiceCount = -1
var randomSeed: Long = -1
val stateMgr = StateManager(7)
while (stateMgr.nextState(context)) {
if (!stateMgr.isFirstState) out.println()
when (stateMgr.state) {
0 -> flashCardSet = expectFlashCardSet(context, flashCardSetFiles)
1 -> tables = expectFlashCardTables(context, flashCardSet!!, "Which table(s) would you like to play with?")
2 -> displayColumnIndices = expectColumnIndices(context, flashCardSet!!, "Which column(s) would you like to see?")
3 -> guessColumnIndices = expectColumnIndices(context, flashCardSet!!, "Which column(s) would you like to guess?")
4 -> {
out.println("How many choices per round?")
rowChoiceCount = Input.expectInt(context, 1, Int.MAX_VALUE)
}
5 -> randomSeed = Input.expectRandomSeed(context, "What seed would you like to use?")
6 -> {
out.println("Game commencing with seed " + java.lang.Long.toHexString(randomSeed) + "...")
out.println()
GuessingGameOptions(Random(randomSeed), flashCardSet!!, tables!!, guessColumnIndices!!, displayColumnIndices!!, rowChoiceCount).playGame(context)
return
}
}
}
}
I.e., a big switch statement to control flow after returning from suspensions. The main difference, however, is that StateManager
supports flows of NORMAL, BACK_ONE, RESTART, EXIT_APP
. The BACK_ONE
is particularly tricky - if I were to remove it, could I effectively implement this using normal coroutines and eliminating the big switch statement? If I were to keep it, would it even be possible without the switch, and if so, how much hackery would be involved? What conventions would I have to follow to assure safety?
My use case is to get useful input from the user and allow stepping back in the process, exiting the app, exiting just a game within the app, etc. Being able to write simple functions that elegantly reflect this sequential process is very appealing. E.g.:
private suspend fun doGuessingGameWithOptionsLoop(context: Context, flashCardSetFiles: Array<File>) {
val out = context.out
val flashCardSet = expectFlashCardSet(context, flashCardSetFiles)
val tables = expectFlashCardTables(context, flashCardSet!!, "Which table(s) would you like to play with?")
val displayColumnIndices = expectColumnIndices(context, flashCardSet, "Which column(s) would you like to see?")
val guessColumnIndices = expectColumnIndices(context, flashCardSet, "Which column(s) would you like to guess?")
out.println("How many choices per round?")
val rowChoiceCount = Input.expectInt(context, 1, Int.MAX_VALUE)
val randomSeed = Input.expectRandomSeed(context, "What seed would you like to use?")
out.println("Game commencing with seed " + java.lang.Long.toHexString(randomSeed) + "...")
out.println()
GuessingGameOptions(Random(randomSeed), flashCardSet, tables!!, guessColumnIndices!!, displayColumnIndices!!, rowChoiceCount).playGame(context)
}
In this case, stepping back would be “safe,” as it’d just be overwriting the old variable. Any bits of code between “expects” would get re-executed, which in this case would be desirable. An all-caps comment on the top indicating the dangerous flow would hopefully be sufficient to prevent errors (in lieu of the language-level support you’d ideally have for something like this.)
Thoughts? Is going backwards impossible? Am I a crazy person? Thanks for reading.