2

I need an heterogeneous, typesafe container to store unrelated type A, B, C.

Here is a kind of type-level specification :

trait Container {
  putA(a: A) 
  putB(b: B)
  putC(c: C)
  put(o: Any) = { o match {
    case a: A => putA(a)
    case b: B => putB(b)
    case c: C => putC(c)
  }
  getAllAs : Seq[A]
  getAllBs : Seq[B]
  getAllCs : Seq[C]
}

Which type is best suites to backed this container ?

Is it worth creating a Containerable[T] typeclass for types A, B, C ?

thks.

Zim-Zam O'Pootertoot
  • 17,888
  • 4
  • 41
  • 69
Yann Moisan
  • 8,161
  • 8
  • 47
  • 91
  • `put(o: Any)` isn't a safe operation if any of `A` and `B` happen to be the same. – Rex Kerr Oct 25 '14 at 09:01
  • I assume `A`, `B`, and `C` are not generic types, because then, at runtime, they will be erased to their upper bound, making your `match`-statement only match against `Any` in the most general case, i.e., everything will be called with `putA`. If they aren't generic and are not subtypes of each other, `put(o: Any)` is still not typesafe however, because you could put a `D`, which would result in a match-error. -- Regarding your question: You could back it with a simple tuple for starters. As it will be abstracted by the interface, you should be able to swap out the implementation anytime. – Kulu Limpa Oct 25 '14 at 09:14
  • 1
    @KuluLimpa - You can do the generic version by `ClassTag`ging everything and then using `isAssignableFrom`. But that doesn't stop all the B's from going int the A's slot if the classes are the same. – Rex Kerr Oct 25 '14 at 09:19
  • @RexKerr Thank you for sharing this. I wasn't aware of the `ClassTag` reflection feature. – Kulu Limpa Oct 25 '14 at 10:02

4 Answers4

7

As other have suggested, you can leverage shapeless' Coproduct type. Here's an example.

// let's define a Coproduct of the two types you want to support
type IS = Int :+: String :+: CNil

// now let's have a few instances
val i = Coproduct[IS](42)
val i2 = Coproduct[IS](43)
val s = Coproduct[IS]("foo")
val s2 = Coproduct[IS]("bar")

// let's put them in a container
val cont = List(i, s, i2, s2)

// now, do you want all the ints?
val ints = cont.map(_.select[Int]).flatten

// or all the strings?
val strings = cont.map(_.select[String]).flatten

// and of course you can add elements (it's a List)
val cont2 = Coproduct[IS](12) :: cont
val cont3 = Coproduct[IS]("baz") :: cont2

Now this is of course not the most intuitive API for a generic container, but can easily encapsulate the logic inside a custom class using a Coproduct for representing the multiple types.

Here's a sketch of an implementation

import shapeless._; import ops.coproduct._

class Container[T <: Coproduct] private (underlying: List[T]) {
  def ::[A](a: A)(implicit ev: Inject[T, A]) =
    new Container(Coproduct[T](a) :: underlying)

  def get[A](implicit ev: Selector[T, A]) =
    underlying.map(_.select[A]).flatten

  override def toString = underlying.toString
}

object Container {
  def empty[T <: Coproduct] = new Container(List[T]())
}

Example

scala> type IS = Int :+: String :+: CNil
defined type alias IS

scala> val cont = 42 :: "foo" :: "bar" :: 43 :: Container.empty[IS]
cont: Container[IS] = List(42, foo, bar, 43)

scala> cont.get[Int]
res0: List[Int] = List(42, 43)

scala> cont.get[String]
res1: List[String] = List(foo, bar)
Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
2

Miles Sabin wrote a post on unboxed union types; this is implemented as a CoProduct in his shapeless library:

shapeless has a Coproduct type, a generalization of Scala's Either to an arbitrary number of choices

I am definitely not an expert on shapeless, but if you create a new question with or edit your question with the shapeless tag then you can get any assistance needed with using CoProduct

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
Zim-Zam O'Pootertoot
  • 17,888
  • 4
  • 41
  • 69
1

You should look at Shapeless's HList or Coproduct; I wouldn't reinvent this myself.

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
0

Here is a first version, but I would to abstract over type :

trait Container {

  def putInt(i: Int)
  def putString(s: String)
  def put(o: Any) = o match {
    case i: Int => putInt(i)
    case s: String => putString(s)
  }
  def getInts() : Seq[Int]
  def getStrings() : Seq[String]

}

class MutableContainer extends Container {

  val ints = mutable.ArrayBuffer[Int]()
  val strings = mutable.ArrayBuffer[String]()

  override def putInt(i: Int): Unit = ints += i

  override def putString(s: String): Unit = strings += s

  override def getStrings(): Seq[String] = strings

  override def getInts(): Seq[Int] = ints

}

object TestContainer extends App {
  val mc = new MutableContainer()
  mc.put("a")
  mc.put("b")
  mc.put(1)
  println(mc.getInts())
  println(mc.getStrings())
}

Now trying to abstract over type

trait Container {
  def getInts() : Seq[Int]
  def getStrings() : Seq[String]

  def put[T](t: T)
  //def get[T] : Seq[T]
}

class MutableContainer extends Container {
  val entities = new mutable.HashMap[Class[_], mutable.Set[Any]]() with mutable.MultiMap[Class[_], Any]

  override def getStrings(): Seq[String] = entities.get(classOf[String]).map(_.toSeq).getOrElse(Seq.empty).asInstanceOf[Seq[String]] //strings
  override def getInts(): Seq[Int] = entities.get(classOf[Int]).map(_.toSeq).getOrElse(Seq.empty).asInstanceOf[Seq[Int]]

  //override def get[T]: Seq[T] = entities.get(classOf[T]).map(_.toSeq).getOrElse(Seq.empty).asInstanceOf[Seq[T]]
  override def put[T](t: T): Unit = entities.addBinding(t.getClass, t)
}

trait Containable[T] {
  def typ : String
}

trait Cont {
  implicit object IntContainable extends Containable[Int] {
    override def typ: String = "Int"
  }
  implicit object StringContainable extends Containable[String] {
    override def typ: String = "String"
  }
}

object TestContainer extends App {
  val mc = new MutableContainer()
  mc.put("a")
  mc.put("b")
  mc.put(1)
  println(mc.getInts())
  println(mc.getStrings())
  println(mc.entities.keys)
}

But i've got a problem with java.lang.Integer and Int…

Yann Moisan
  • 8,161
  • 8
  • 47
  • 91