Say you have a class Person, and create a collection class for it by extending e.g. ArrayBuffer:
class Persons extends ArrayBuffer[Person] {
// methods operation on the collection
}
Now, with ArrayBuffer, can create a collection with the apply() method on the companion object, e.g.:
ArrayBuffer(1, 2, 3)
You want to be able to do the same with Persons, e.g.:
Persons(new Person("John", 32), new Person("Bob", 43))
My first intuition here was to extend the ArrayBuffer companion object and getting the apply() method for free. But it seems that you can't extend objects. (I'm not quite sure why.)
The next idea was to create a Persons object with an apply() method that calls the apply method of ArrayBuffer:
object Persons {
def apply(ps: Person*) = ArrayBuffer(ps: _*)
}
However, this returns an ArrayBuffer[Person] and not a Persons.
After some digging in the scaladoc and source for ArrayBuffer, I came up with the following, which I thought would make the Persons object inherit apply() from GenericCompanion:
EDIT:
object Persons extends SeqFactory[ArrayBuffer] {
def fromArrayBuffer(ps: ArrayBuffer[Person]) = {
val persons = new Persons
persons appendAll ps
persons
}
def newBuilder[Person]: Builder[Person, Persons] = new ArrayBuffer[Person] mapResult fromArrayBuffer
}
However, it gives the following error message:
<console>:24: error: type mismatch;
found : (scala.collection.mutable.ArrayBuffer[Person]) => Persons
required: (scala.collection.mutable.ArrayBuffer[Person(in method newBuilder)])
=> Persons
def newBuilder[Person]: Builder[Person, Persons] = new ArrayBuffer[Perso
n] mapResult fromArrayBuffer
^
Perhaps this should disencourage me from going further, but I'm having a great time learning Scala and I'd really like to get this working. Please tell me if I'm on the wrong track. :)
Rather than extending ArrayBuffer[Person]
directly, you can use the pimp my library pattern. The idea is to make Persons
and ArrayBuffer[Person]
completely interchangeable.
class Persons(val self: ArrayBuffer[Person]) extends Proxy {
def names = self map { _.name }
// ... other methods ...
}
object Persons {
def apply(ps: Person*): Persons = ArrayBuffer(ps: _*)
implicit def toPersons(b: ArrayBuffer[Person]): Persons = new Persons(b)
implicit def toBuffer(ps: Persons): ArrayBuffer[Person] = ps.self
}
The implicit conversion in the Persons companion object allows you to use any ArrayBuffer method whenever you have a Persons reference and vice-versa.
For example, you can do
val l = Persons(new Person("Joe"))
(l += new Person("Bob")).names
Note that l
is a Persons
, but you can call the ArrayBuffer.+=
method on it because the compiler will automatically add in a call to Persons.toBuffer(l)
. The result of the +=
method is an ArrayBuffer
, but you can call Person.names
on it because the compiler inserts a call to Persons.toPersons
.
Edit:
You can generalize this solution with higher-kinded types:
class Persons[CC[X] <: Seq[X]](self: CC[Person]) extends Proxy {
def names = self map (_.name)
def averageAge = {
self map (_.age) reduceLeft { _ + _ } /
(self.length toDouble)
}
// other methods
}
object Persons {
def apply(ps: Person*): Persons[ArrayBuffer] = ArrayBuffer(ps: _*)
implicit def toPersons[CC[X] <: Seq[X]](c: CC[Person]): Persons[CC] =
new Persons[CC](c)
implicit def toColl[CC[X] <: Seq[X]](ps: Persons[CC]): CC[Person] =
ps.self
}
This allows you to do things like
List(new Person("Joe", 38), new Person("Bob", 52)).names
or
val p = Persons(new Person("Jeff", 23))
p += new Person("Sam", 20)
Note that in the latter example, we're calling +=
on a Persons
. This is possible because Persons
"remembers" the underlying collection type and allows you to call any method defined in that type (ArrayBuffer
in this case, due to the definition of Persons.apply
).
Apart from anovstrup's solution, won't the example below do what you want?
case class Person(name: String, age: Int)
class Persons extends ArrayBuffer[Person]
object Persons {
def apply(ps: Person*) = {
val persons = new Persons
persons appendAll(ps)
persons
}
}
scala> val ps = Persons(new Person("John", 32), new Person("Bob", 43))
ps: Persons = ArrayBuffer(Person(John,32), Person(Bob,43))
scala> ps.append(new Person("Bill", 50))
scala> ps
res0: Persons = ArrayBuffer(Person(John,32), Person(Bob,43), Person(Bill,50))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With