3

Scala allows to define update such as

def update(index: Int, value: String) { ... }

and then call it like

foo(i) = "Text"

Is there a trait that encapsulates that? Something like

trait Update1[+A,+B] {
    def update(i: A, v: B)
}

(Of course I could define such a trait myself, but it would only work for instances that I mix with it, not with other ones constructed beyond my influence.)

José González
  • 1,207
  • 12
  • 25
Petr
  • 62,528
  • 13
  • 153
  • 317
  • 1
    Why would you need a trait? It's a syntactic feature, it doesn't require type-level support. – Jean-Philippe Pellet Jul 22 '13 at 15:15
  • @Jean-PhilippePellet I'm creating generic functions that can read values (using an arbitrary `Function1`) as well as update them. If there were a type-level support for it, I could use them for anything that is updatable, not just for classes I create myself. – Petr Jul 22 '13 at 15:38
  • I have tried `type Update1[A+, B+] = { def update(i: A, v:B): Unit }`, but [this is not allowed by the compiler](http://stackoverflow.com/questions/7830731/parameter-type-in-structural-refinement-may-not-refer-to-an-abstract-type-defin), so I tried to declare a specific one `type Update1 = { def update(i: Int, v: String): Unit }` but for some reason this still doesn't work; I tested on ArrayBuffer[String] and got: `java.lang.NoSuchMethodException: scala.collection.mutable.ArrayBuffer.update(int, java.lang.String)`. Not sure why, but you might want to look in this direction. – Mortimer Sep 04 '13 at 13:22
  • ok, the reason why `Update1` didn't work in this case is that at runtime, `String` is erased, and the signature of the value is `Object`, so you could try `type Update1 = { def update(i: Int, v: Object): Unit }`. While you can successfully cast `ArrayBuffer[String]` to `Update1`, you still need to do it explicitly, so I am not sure if it's very useful for your use case. – Mortimer Sep 04 '13 at 15:13

1 Answers1

1

The reason why you can't define such a trait is that you are using covariant type parameters in a place where they are not allowed. The following traits compile fine in Scala 2.10:

trait Update1[-A, -B] {
  def update(i: A, v: B) : Unit
}

trait MyFunction1[-A, +B] {
  def apply(a:A): B
}

trait Mutable[-A, B] extends Update1[A,B] with MyFunction1[A,B]

Notice that in order to have a mutable trait you have to fix the B parameter, so it allows neither covariance nor contravariance. If you take a look at the mutable collections in the Scala API you can see that in fact this is how they are declared.

In addition, nothing prevents you to mix a trait in an object instead of in a class to make the compiler happy, if you know that class already implements the methods defined in the trait. For example, you can have the following:

class SingleStringCollection(v: String) extends MyFunction1[Int, String] {
  private var someString: String = v
  def apply(a: Int): String =  someString

  def update(i: Int, v: String): Unit = {
    someString = v
  }
  override def toString = someString
}

val test1: Update1[Int, String] = new SingleStringCollection("hi") // this would fail
val test2: Update1[Int, String] = new SingleStringCollection("hi") with Update1[Int, String] // this would work

Or you could also use structural typing if you just want to require that your val or parameter implements a list of known methods:

type UpdatableStructuralType = {
  def update(i: Int, v: String) : Unit
}

val test3: UpdatableStructuralType = new SingleStringCollection("hi") // this would work
test3(0) = "great" // And of course this would also work

So you have several alternatives if you want to accept parameters conforming to some trait or requiring some methods to be implemented.

José González
  • 1,207
  • 12
  • 25