Using 'nested' types in a list in Scala

392 views Asked by At

I'm using ScalaFX and JavaFX, and have this code:

import scalafx.Includes._

class Type1(anInt: Int, ...)
class Type2(aString: String, ...)

class ListItem[T](internalValue:T, ...)

object Worker
{

   val list1 = FXCollections.observableArrayList[ListItem[Type1]]()
   val list2 = FXCollections.observableArrayList[ListItem[Type2]]()

   def workWithList(list:ObservableList[ListItemType]) {
      list.foreach(i => workWithItem(i))
   }

   def workWithItem(item:ListItem) {
      item match {
         case i:ListItem[Type1] => do something
         case i:ListItem[Type2] => do something else
      }
   }    

   workWithList(list1)
   workWithList(list2)

}

My problem is that this code doesn't compile; it says that I can't use ObservableList[ListItem[Type1]] for the workWithList method, which expects ObservableList[ListItem].

As I've been playing with this, some variations of this code says that there are unchecked warnings, and that pattern matching won't work due to type erasure.

Ideally:

  • there would be just a single list that could hold objects of type ListItem[Type1] and ListItem[Type2]
  • I could do pattern matching when working with the items to do different things depending on what kind of item is being worked with
  • workWithItem could work with either type of item. In my current code I've had to change the signature to workWithItem(item:ListItem[_]) and then do workWithItem(someItem.asInstanceOf[ListItem[_]]). Probably not the correct thing to do!

Thanks!

2

There are 2 answers

2
Shadowlands On BEST ANSWER

The method signature for workWithList looks wrong - where does the ListItemType type come from? Should this be def workWithList(list: ObservableList[ListItem[_]]) { ...?

If so, then the problem you will run up against in the match cases is that due to type erasure, the JVM can't tell the difference at runtime between the type signatures of the cases. This can be worked around by, for example, turning the Type1, Type2 and ListItem into case classes (or manually generating unapply methods for them), then deconstructing the item in the match cases, like so:

case class Type1(anInt: Int)
case class Type2(aString: String)

case class ListItem[T](internalValue:T)

object Worker
{

   val list1 = FXCollections.observableArrayList[ListItem[Type1]]()
   val list2 = FXCollections.observableArrayList[ListItem[Type2]]()

   def workWithList(list: ObservableList[ListItem[_]]) {
      list.foreach(i => workWithItem(i))
   }

   def workWithItem(item: ListItem[_]) {
      item match {
         case ListItem(i: Type1) => println(s"Have type 1: ${i.anInt}") //do something
         case ListItem(i: Type2) => println(s"Have type 2: ${i.aString}") //do something else
         case anythingElse => println(s"Unknown type: $anythingElse") //just as a safe default for now
      }
   }

   workWithList(list1)
   workWithList(list2)

}

Note that I am working here without specific knowledge of the FX libraries (I tried this using straight scala Lists rather than ObservableList or FXCollections.observableArrayList), so they may affect the applicability of this solution (and might be where ListItemType is defined).

The method signature for workWithItem is fine, but the asInstanceOf cast you tried shouldn't be required.

2
user3603546 On

You are attacking mosquito with a shotgun. This example can be solved without parametric polymorphism, with plain old inheritance:

import scalafx.Includes._
import javafx.collections.{FXCollections,ObservableList}

class ListItemType
class Type1(anInt: Int) extends ListItemType
class Type2(aString: String) extends ListItemType

class ListItem(val internalValue:ListItemType)

object Worker
{

   val list1 = FXCollections.observableArrayList[ListItem]()
   val list2 = FXCollections.observableArrayList[ListItem]()

   def workWithList(list:ObservableList[ListItem]) {
      list.foreach(i => workWithItem(i))
   }

   def workWithItem(item:ListItem) {
      item.internalValue match {
         case i:Type1 => println("do something")
         case i:Type2 => println("do something else")
      }
   }    

   workWithList(list1)
   workWithList(list2)

}

No errors, no warnings, and you can mix both types of objects in the same list.