I'm trying to use something like the visitor pattern, but with return values.
However, although there are no explicit casts, I'm getting a ClassCastException:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.CharSequence;
at Printer.combine(...)
at Split.accept(...)
at MWEKt.main(...)
Code:
interface TreeElem {
fun <T> accept(visitor: TreeVisitor<T>): T
}
class Leaf: TreeElem {
override fun <T> accept(visitor: TreeVisitor<T>): T {
return visitor.visit(this)
}
}
class Split(val left: TreeElem, val right: TreeElem): TreeElem {
override fun <T> accept(visitor: TreeVisitor<T>): T {
return visitor.combine( // this causes cast error
visitor.visit(this),
left.accept(visitor),
right.accept(visitor))
}
}
interface TreeVisitor<T> {
// multiple implementations with different T in future (only one in this example)
fun visit(tree: Leaf): T
fun visit(tree: Split): T
fun combine(vararg inputs: T): T
}
class Printer: TreeVisitor<CharSequence> {
override fun combine(vararg inputs: CharSequence): CharSequence { // error here
return inputs.joinToString(" ")
}
override fun visit(tree: Leaf): CharSequence { return "leaf" }
override fun visit(tree: Split): CharSequence { return "split" }
}
fun main(args: Array<String>) {
val tree = Split(Leaf(), Leaf())
val printer = Printer()
println(tree.accept(printer))
}
I don't know what the problem is. Am I trying to do something impossible, or am I failing to express it correctly, or is type erasure making something that should be possible impossible?
My thoughts so far:
Printer.combine
expects CharSequence
s;TreeElem.accept
that returns CharSequence
Since the last point is in conflict with realist, I'm probably understanding something incorrectly.
EDIT: I've translated the MWE to Java to see if it's a Kotlin issue and to attract an answer:
interface TreeElem {
<T> T accept(TreeVisitor<T> visitor);
}
class Leaf implements TreeElem {
public <T> T accept(TreeVisitor<T> visitor) {
return visitor.visit(this);
}
}
class Split implements TreeElem {
private TreeElem left;
private TreeElem right;
Split(TreeElem left, TreeElem right) {
this.left = left;
this.right = right;
}
public <T> T accept(TreeVisitor<T> visitor) {
return visitor.combine(
visitor.visit(this),
left.accept(visitor),
right.accept(visitor));
}
}
interface TreeVisitor<T> {
T visit(Leaf tree);
T visit(Split tree);
T combine(T... inputs);
}
class Printer implements TreeVisitor<CharSequence> {
public CharSequence combine(CharSequence... inputs) {
StringBuilder text = new StringBuilder();
for (CharSequence input : inputs) {
text.append(input);
}
return text;
}
public CharSequence visit(Leaf tree) { return "leaf"; }
public CharSequence visit(Split tree) { return "split"; }
}
public class MWEjava {
public static void main(String[] args) {
TreeElem tree = new Split(new Leaf(), new Leaf());
Printer printer = new Printer();
System.out.println(tree.accept(printer));
}
}
The error is the same for the Java case.
I'm pretty sure this is a duplicate of this question: https://stackoverflow.com/a/9058259/4465208
However, to provide a specific solution, you could replace the vararg
argument with a List<T>
instead, which will work just fine:
class Split(val left: TreeElem, val right: TreeElem) : TreeElem {
override fun <T> accept(visitor: TreeVisitor<T>): T {
return visitor.combine(listOf(
visitor.visit(this),
left.accept(visitor),
right.accept(visitor)))
}
}
interface TreeVisitor<T> {
fun combine(inputs: List<T>): T
// ...
}
class Printer : TreeVisitor<CharSequence> {
override fun combine(inputs: List<CharSequence>): CharSequence {
return inputs.joinToString(" ")
}
// ...
}
Not as pretty, but it plays nice with generics.