Kotlin 47
I want to move the testing bits for Imperative back down to the test branch, segment, section, whatever it’s called.
The issue, I reckon, is that to make it unnecessary to change a bunch of tests, I’ve defaulted some parameters in the Imperative family to test values. When I moved the code up to main, not much could be left in test, because I can’t reference from main to test. Good reason. So let’s see what we have to do.
class ImperativeFactory(private val verbs: Verbs, private val synonyms: Synonyms = Synonyms(TestSynonymTable)) {
constructor(map: Map<String, Imperative>) : this(Verbs(map))
OK, that won’t do. I’ll remove the default and let IDEA guide me to fixes. I think I could do something clever here, but let’s see what level of complaints we see.
class ImperativeFactory(private val verbs: Verbs, private val synonyms: Synonyms) {
constructor(map: Map<String, Imperative>) : this(Verbs(map))
IDEA complains, looking at the constructor:
Type mismatch: inferred type is Verbs but Map<String, Imperative> was expected
I’m not even sure what that constructor is about. Let’s remove it and see what complaints we get. IDEA or Ron, or possibly both, are confused.
I get errors saying that I’ve not passed a Synonyms. That’s for sure. I’ll fix those:
@Test
fun `look up some imperatives`() {
val imperatives = ImperativeFactory(Verbs(TestVerbTable), Synonyms(TestSynonymTable))
var imp: Imperative = imperatives.fromOneWord("east")
assertThat(imp).isEqualTo(Imperative("go","east"))
imp = imperatives.fromOneWord("e")
assertThat(imp).isEqualTo(Imperative("go","east"))
}
And similar cases. After those (two) are fixed, there’s this:
@Test
fun `two word legal command`() {
val imperatives = ImperativeFactory(TestVerbTable)
val imp = imperatives.fromTwoWords("go", "e")
assertThat(imp.act(testLex())).isEqualTo("went east")
}
This was an early test while I was building things up. I’ll disable some of these, just as an excuse to look at them all. No, that’s useless, they still won’t compile. Bad idea. I’ll just paste my working constructor everywhere. Some of these tests are probably redundant now, old scaffolding. Let’s get to green, then see what we can improve.
That works nicely. Now I can move my tables back to the test file, I think. It lets me move the TestVerbTable and TestSynonymTable. TestActionsTable can’t move. Test. Tests don’t run. IDEA misled me. Let’s see references to those tables.
For the synonym table, all references seem to already be in the test file. Try moving it. OK, tests green. Commit: Move testSynonymTable back to tests.
Try moving the verb table again. This time it goes. Green. Commit: move TestVerbTable back to tests. Weird. No idea why it didn’t work last time. Now left with actions, and find this:
val TestActionTable = mapOf(
"go" to { imp: Imperative -> imp.say("went ${imp.noun}")},
"say" to { imp: Imperative -> imp.say("said ${imp.noun}")},
"inventory" to { imp: Imperative -> imp.say("You got nothing")}
).withDefault {it ->{imp: Imperative -> imp.say("I can't ${imp.verb} a ${imp.noun}") }}
class Actions(private val verbMap:Map<String, Action> = TestActionTable) {
fun act(imperative: Imperative) {
verbMap.getValue((imperative.verb))(imperative)
}
}
Another default parameter. I may have to give up doing that lazy trick. Anyway remove it and fix the code that complains.
Nothing complains. Fascinating. Looks like all my actual tests may be doing it right. Move it. Tests are green. Commit: move TestActionTable back to test.
So that’s nice. I’d like to see all the places those tables are referenced. I suspect there’s duplication to be removed.
To reduce references to the test tables, I create this helper method in the test:
fun getFactory() = ImperativeFactory(Verbs(TestVerbTable), Synonyms(TestSynonymTable))
And apply it everywhere where I create a factory. I imagine there’s a way to set up a before or setup here, but my tests aren’t really amenable to that, and I don’t want to learn something right now, I’m tidying.
I do the same with all the tables:
class ImperativeTest {
private fun getFactory() = ImperativeFactory(getVerbs(), getSynonyms())
private fun getVerbs() = Verbs(TestVerbTable)
private fun getSynonyms() = Synonyms(TestSynonymTable)
private fun getActions() = Actions(TestActionTable)
Green. Commit once more. I’ve renamed lots of things private. I wonder if IDEA and Kotlin have any hints for me here. Yes, it weakly suggests another private, and advises renaming it
to _
in somewhere.
I thin this is more squeaky than I had even intended, so let’s reflect. What have I learned?
Reflection
I learned that F2 takes you to the next thing that IDEA/Kotlin are concerned about. I learned that smaller moves are probably better, since I still don’t know why my larger ones didn’t work. I relearned that when something weird happens after you make a big change or allow IDEA to make one, reverting is a good idea.
I learned that I wish I would do a commit after each time the tests run green. If I could automate that, I would. (If there is a way to automate it, please let me know.)
And I’m wondering about Lexicon. I’ve got this nice object:
class Lexicon(val synonyms: Synonyms, val verbs: Verbs, val actions: Actions) {
fun synonym(word:String):String = synonyms.synonym(word)
fun translate(word: String): Imperative = verbs.translate((word))
fun act(imperative: Imperative) = actions.act(imperative)
}
It’s intended, if I am not mistaken (which assumes facts not in evidence), to be a single parameter to be passed to the ImperativeFactory, and used by it to create the Imperative, and then passed to the Imperative to get things done. The exchange strikes me as a bit odd:
data class Imperative(val verb: String, val noun: String) {
fun act(lexicon: Lexicon):String {
lexicon.act(this)
return said
}
class Lexicon(val synonyms: Synonyms, val verbs: Verbs, val actions: Actions) {
fun act(imperative: Imperative) = actions.act(imperative)
Presently, the Imperative doesn’t know the Lexicon, in fact no one does. Let’s change that. Let’s change ImperativeFactory to have a Lexicon.
class ImperativeFactory(private val verbs: Verbs, private val synonyms: Synonyms) {
Where I think I’m going, you may be asking, is to have the lexicon be a single holder for all the vocabulary of the game, and to have it passed to the Imperative in due course. So I’ going to change the signature of ImperativeFactory, and tick through the changes required. I think IDEA will help me with that nicely.
My first try at that left me confused. I think it’ll help to build this function first:
private fun getLexicon() = Lexicon(getSynonyms(),getVerbs(),getActions())
Now let’s redo the signature change.
class ImperativeFactory(val lexicon: Lexicon) {
fun fromOneWord(verb:String): Imperative = imperative(verb)
fun fromTwoWords(verb:String, noun:String) = imperative(verb).setNoun(synonym(noun))
private fun imperative(verb: String) = lexicon.translate(synonym(verb))
private fun synonym(verb: String) = lexicon.synonym(verb)
I am sure there must be some errors but it didn’t tell me of any. Curious. The tests are green. How smart is this thing? I look for all my uses of ImperativeFactory. There’s just the one, thanks to my consolidation above, and IDEA has fixed it:
private fun getFactory() = ImperativeFactory(getLexicon())
That is downright impressive. Well done, IntelliJ!
Commit: ImperativeFactory is created with a Lexicon.
Twenty minutes until salmon dinner. Let’s see about creating Imperative with a Lexicon. It looks like this now:
data class Imperative(val verb: String, val noun: String) {
I am pretty sure that I use the fact that Imperative is a data class to compare it in tests. That means that I will have to give up that feature to provide it with a standard lexicon. We could inject it later or something. For now, too close to supper, we’ll close this out.
Summary
Objects are consolidating and are mostly quite small. We’re not there yet, but this is shaping up well.
See you next time!