I tried various things to do something quite simple that I would do using a decorator pattern in Java, but wanted to do with stackable modifications in Scala and failed.
Here's my use-case: I'm implementing some simple auto-completers. They all implement a base trait:
trait AutoCompleter {
def topSuggestions(prefix: String): Iterator[String]
def update(sentence: String*): Unit
final def topSuggestions(prefix: String, n: Int): List[String] = topSuggestions(prefix).take(n).toList
}
Some of them are concrete implementations based on a custom implementation of a trie:
/**
* This auto-completer learns from the user's input, and therefore does not require a preliminary dictionary.
*/
class ParrotAutoCompleter extends AutoCompleter {
private val trie = new WeightedTrie[Char, String]()
override def topSuggestions(prefix: String): Iterator[String] = trie.prefixedBy(prefix)
override def update(sentence: String*): Unit = sentence.foreach(trie += _)
}
And some others are stackable modifications:
/**
* This auto-completer will try returning some suggestions when the prefix did not match any known word by dropping the
* last character until it finds a suggestion
*/
trait TolerantAutoCompleter extends AutoCompleter {
def MaxRetries: Int
abstract override def topSuggestions(prefix: String): Iterator[String] = {
if (MaxRetries < 1) throw new IllegalArgumentException("Should allow 1 retry minimum, but max retries was: " + MaxRetries)
for (attempt <- 0 to Math.min(prefix.length, MaxRetries)) {
val suggestions = super.topSuggestions(prefix.substring(0, prefix.length - attempt))
if (suggestions.hasNext) return suggestions
}
Iterator()
}
}
And I use them like this:
val autoCompleter = new ParrotAutoCompleter with TolerantAutoCompleter { override val MaxRetries: Int = 5 }
This implementation works all fine, but it has a flaw: the sanity check performed on MaxRetries
is done late, only when using the auto-completer rather than when creating it. More anecdotically, it is ran every time instead of just once.
The problem is that any code outside a method in a trait gets executed right away, even before MaxRetries
has been overriden (independently of whether it is declared as a val
or a def
).
How could I perform my sanity check at construction time, after the override, and without losing the property of being a stackable modification ?