I'm having trouble with type mismatches when trying to write a function that takes as input (and output) an object that extends an abstract class.
Here is my abstract class:
abstract class Agent {
type geneType
var genome: Array[geneType]
}
Here is my function:
def slice[T <: Agent](parentA: T, parentB: T):(T, T) = {
val genomeSize = parentA.genome.length
// Initialize children as identical to parents at first.
val childA = parentA
val childB = parentB
// the value 'index' is sampled randomly between 0 and
// the length of the genome, less 1.
// This code omitted for simplicity.
val index;
val pAslice1 = parentA.genome.slice(0, index + 1)
val pBslice1 = parentB.genome.slice(index + 1, genomeSize)
val genomeA = Array.concat(pAslice1, pBslice1)
childA.genome = genomeA
// And similary for childB.
// ...
// ...
return (childA, childB)
}
I'm receiving an error (I'm running this with sbt, by the way) as follows:
[error] .......... type mismatch;
[error] found : Array[parentA.geneType]
[error] required: Array[T#geneType]
I'm not sure what the problem is, as I'm new to abstract classes, generic type parametrization, and probably other relevant concepts whose names I don't know.
In your construction it is well possible that parentA
and parentB
are different types, T
only gives you an upper bound (they must be at least as specific as T). Arrays are invariant in their element type, thus you cannot exchange the elements in a sound way here.
A second problem with your code is that you are returning objects of type T
, but actually you are mutating the input arguments. Either you want mutation, then declare the method's return type Unit
to make that clear; or create new instances of T
and make Agent
immutable. It depends on your performance requirements, but I would always try the immutable variant first, because it is easier to reason about.
Here is mutable variant. Note that because arrays are special objects on the JVM (no type erasure happening), you need to provide a so-called class-tag for them as well:
abstract class Agent {
type geneType
var genome: Array[geneType]
implicit def geneTag: reflect.ClassTag[geneType]
}
def slice[A](parentA: Agent { type geneType = A },
parentB: Agent { type geneType = A }): Unit = {
val genomeSize = parentA.genome.length
require (parentB.genome.length == genomeSize)
import parentA.geneTag
val index = (math.random * genomeSize + 0.5).toInt
val (aInit, aTail) = parentA.genome.splitAt(index)
val (bInit, bTail) = parentB.genome.splitAt(index)
val genomeA = Array.concat(aInit, bTail)
val genomeB = Array.concat(bInit, aTail)
parentA.genome = genomeA
parentB.genome = genomeB
}
Here you require that parentA
and parentB
share an exactly defined gene-type A
. You can define a type alias to simplify specifying that type:
type AgentT[A] = Agent { type geneType = A }
def slice[A](parentA: AgentT[A], parentB: AgentT[A]): Unit = ...
To preserve the parents and create new children, the easiest would be to add a copy method to the Agent
class:
abstract class Agent {
type geneType
var genome: Array[geneType]
implicit def geneTag: reflect.ClassTag[geneType]
def copy(newGenome: Array[geneType]): AgentT[geneType]
}
type AgentT[A] = Agent { type geneType = A }
def slice[A](parentA: AgentT[A], parentB: AgentT[A]): (AgentT[A], AgentT[A]) = {
val genomeSize = parentA.genome.length
require (parentB.genome.length == genomeSize)
import parentA.geneTag
val index = (math.random * genomeSize + 0.5).toInt
val (aInit, aTail) = parentA.genome.splitAt(index)
val (bInit, bTail) = parentB.genome.splitAt(index)
val genomeA = Array.concat(aInit, bTail)
val genomeB = Array.concat(bInit, aTail)
(parentA.copy(genomeA), parentB.copy(genomeB))
}
If you don't need to squeeze the last bits of performance, you could use an immutable collection such as Vector
instead of Array
.
case class Agent[A](genome: Vector[A]) {
def size = genome.size
}
def slice[A](parentA: Agent[A], parentB: Agent[A]): (Agent[A], Agent[A]) = {
val genomeSize = parentA.size
require (parentB.size == genomeSize)
val index = (math.random * genomeSize + 0.5).toInt
val (aInit, aTail) = parentA.genome.splitAt(index)
val (bInit, bTail) = parentB.genome.splitAt(index)
val genomeA = aInit ++ bTail
val genomeB = bInit ++ aTail
(parentA.copy(genomeA), parentB.copy(genomeB))
}