14

I'm moving my first steps in Scala and I would like to make the following code works:

trait Gene[+T] {
    val gene: Array[T]
}

The error that the compiler gives is: covariant type T occurs in invariant position in type => Array[T] of value gene

I know I could do something like:

trait Gene[+T] {
    def gene[U >: T]: Array[U]
}

but this doesn't solve the problem because I need a value: pratically what I'm trying to say is "I don't care of the inside type, I know that genes will have a gene field that return its content". (the +T here is because I wanna do something like type Genome = Array[Gene[Any]] and then use it as a wrapper against the single gene classes so I can have a heterogeneous array type) Is it possible to do it in Scala or I'm simply taking a wrong approach? Would it be better to use a different structure, like a Scala native covariant class?

Thanks in advance!

P.S.: I've also tried with class and abstract class instead than trait but always same results!

EDIT: with kind suggestion by Didier Dupont I came to this code:

package object ga {


  class Gene[+T](val gene: Vector[T]){

    def apply(idx: Int) = gene(idx)

    override def toString() = gene.toString

  }

  implicit def toGene[T](a: Vector[T]) = new Gene(a)

  type Genome = Array[Gene[Any]]

}

package test

import ga._

object Test {
    def main(args: Array[String]) {
        val g = Vector(1, 3, 4)

        val g2 = Vector("a", "b")

        val genome1: Genome = Array(g, g2)

        println("Genome")

        for(gene <- genome1) println(gene.gene) 
    }
}

So I now think I can put and retrieve data in different types and use them with all type checking goodies!

Vincenzo Maggio
  • 3,787
  • 26
  • 42

2 Answers2

14

Array is invariant because you can write in it.

Suppose you do

val typed = new Gene[String]
val untyped : Gene[Any] = typed // covariance would allow that
untyped.gene(0) = new Date(...)

this would crash (the array in your instance is an Array[String] and will not accept a Date). Which is why the compiler prevents that.

From there, it depends very much on what you intend to do with Gene. You could use a covariant type instead of Array (you may consider Vector), but that will prevent user to mutate the content, if this was what you intended. You may also have an Array inside the class, provided it is decladed private [this] (which will make it quite hard to mutate the content too). If you want the client to be allowed to mutate the content of a Gene, it will probably not be possible to make Gene covariant.

Didier Dupont
  • 29,398
  • 7
  • 71
  • 90
  • Nope, I don't need to mutate it and I indeed though about Vector. My main requirement is having the client code managing an array of different Gene[T], but still with type constraints on operations. I know it's difficult and by being still at my first steps maybe I simply thinking too functional, or too dynamic, but I intend to develop something bigger and this could be a requirement: automatic boxing and unboxing of values. If you want I can rephrase the question! – Vincenzo Maggio Jul 30 '12 at 13:27
  • Please do. What you do will not work, but not knowing what you need, it is difficult to help more. Is performance the reason why you want an Array? What is the client expected to do with an heterogenous collection of Gene[T]? – Didier Dupont Jul 30 '12 at 14:23
  • Edited my answer. Please review it because I think that thanks to your help I've found a solution. Obviously I'm going to accept your answer ;) – Vincenzo Maggio Jul 30 '12 at 15:02
  • In your code snippet there is a typo I think. You declared `typed` but didn't use it. It should be on the second line instead of `gene` right? – Tomas Lazaro Jul 30 '12 at 18:59
3

The type of gene needs to be covariant in its type parameter. For that to be possible, you have to choose an immutable data structure, for example list. But you can use any data structure from the scala.collection.immutable package.

Kim Stebel
  • 41,826
  • 12
  • 125
  • 142