3

I am frequently pushing the limits of Java's type system through my use of Guice, TypeLiteral, generics, and wildcards. I often run into situations where I need to perform unchecked casts, which pretty much ruins type safety--in other words, "Generics Hell."

Here's a simplified example of some of my problematic Java code.

class SquareDrawer implements ShapeDrawer<Row<Square>> {}
class Client {
   Key<SquareDrawer> SQUARE_DRAWER_KEY = 
      Key.get(SquareDrawer.class, randomAnnotation());
   void bindShapeDrawer(
      Key<? extends ShapeDrawer<Row<? extends Shape>>> shapeDrawer) {}

   Client() {
      // Note Unchecked cast required below
      bindShapeDrawer( 
         (Key<? extends ShapeDrawer<Row<? extends Shape>>>) SQUARE_DRAWER_KEY);
   }
}

I've been learning Scala and have been under the impression (or illusion) that it has better support for generics than Java. Could the above code be written in Scala to avoid the unchecked casts?

Is there still a need for Guice's TypeLiteral in Scala?

Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246

3 Answers3

3

There are a couple of things Scala offers.

  • Higher kinded types (I hope I use the term correctly) allow you to define things like 'any type having another type as a type parameter' afaik there is no way to express that in java

  • Co and Contravariant type parameters. In java you can make parameters one or the other by using wildcards in every place where they are used. In Scala you just declare them as such.

  • Type witnesses (Again: Is that the correct term?) are implicit functions that demonstrate some property of type arguments, thereby defining constraints on the type. If an implicit conversion matching the declaration of the witness exists, the call will compile the condition holds.

  • Path dependent types. You can have types that are elements of instances, so each instance has its own type. Again you can't do this in java afaik.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
2

Scala has a form of reified types called Manifests. They let you do things that would be quite clunky in Java due to type erasure. Read all about them here: http://www.scala-blogs.org/2008/10/manifests-reified-types.html

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • Except that Manifests aren't reification. They simply tell the compiler that it doesn't need to complain about certain uses of generics (like dynamically instantiating them) by showing the compiler all the different code paths that could actually be taken in the scope of the manifested generic. – Destin Jan 18 '12 at 17:53
  • @soc http://stackoverflow.com/questions/3587286/how-does-scalas-2-8-manifest-work – Destin Jan 19 '12 at 00:08
  • 1
    @Destin: `T : Manifest` creates just another method argument, which is implicitly passed by the compiler. No idea what you want to tell me with that link. – soc Jan 19 '12 at 00:19
2

The following (hopefully) equivalent Scala code compiles without errors. Maybe I need to state that it contains no dynamic casts. Note that I had to make Key covariant in its type argument, because SquareDrawer is only a subtype of ShapeDrawer[Row[Square]].

trait ShapeDrawer[A]
trait Row[A]
trait Shape
trait Square extends Shape
trait Key[+A]

//your code starts here
trait SquareDrawer extends ShapeDrawer[Row[Square]]

class Client{
  val SDK = new Key[SquareDrawer]{}

  bindShapeDrawer(SDK)

  def bindShapeDrawer[SD[A] <: ShapeDrawer[A],S <: Shape](shapeDrawer: Key[SD[Row[S]]]) {}
}
ziggystar
  • 28,410
  • 9
  • 72
  • 124
  • Thanks! So if one were to call `bindShapeDrawer(new Key[ProgramDrawer]{})` where `ProgramDrawer` is defined as `trait ProgramDrawer extends ShapeDrawer[Row[Program]]` and `Program` is defined as `trait Program`, would Scala correctly report a compile-time type error? – Jeff Axelrod Jan 18 '12 at 20:26