2

Below is a specific instance of a kind of situation that I sometimes encounter with parameterized types. Basically, there are type parameters that I know are compatible, but I don't know how to prove that to some parts of the code.

I'm writing a request router that maps urls to handler functions. Below is some simplified code. I create a List[Route] where a Route is basically a UrlMatcher, Function pair.

class Route[A](matcher: UrlMatcher[A], handler: HandlerFunction[A])

abstract class UrlMatcher[A] {
   def match(url: String): Option[A]   // None if no match

The type parameter A is for the "arguments" that the matcher might extract from the URL. They would be passed to the handler function. For example, a UrlMatcher[Int] that sees a URL path like "/users/123" could pass 123 to a getUser(id: Int) function. The router might look like:

val routes = ArrayBuffer[Route[_]]

def callHandler(url: String) {
  for (r <- routes) {
    val args = r.matcher.matchUrl(url)
    if (args.isDefined)
      r.handler(args.get)  // <--- error here
    }

The problem is that I get type mismatch errors because I don't know how to tell it the two types are the same.

type mismatch; found: args.type (with underlying type Option[Any])  
            required: _$1  

I know I can redesign it so that Route has a method like matchAndCall, but I'd like to keep this logical flow if possible.

Update/Edit

I don't fully understand existential types, but I tried this...

val routes = ArrayBuffer[T forSome { type T }]()

And it removed the mismatch error above. However, I have another one where I was inserting into the ArrayBuffer.

def route[P](matcher: UrlMatcher[P], handler: Handler[P]): AbstractRoute = {
  val route = new Route(matcher, handler)
  otherRoutes.append(route)   // error here  
  route
}

Now the error is...

type mismatch;  found : Route[P]  required: Route[T forSome { type T }] Note: P <: T
forSome { type T }, but class Route is invariant in type P. You may wish to define 
P as +P instead. (SLS 4.5) 

Why is P incompatible with T, since their are no constraints on T?

Rob N
  • 11,371
  • 11
  • 72
  • 126

1 Answers1

3

This is one of the reasons existential types (the Scala equivalent of wildcard types) are a Bad Thing (TM) that is best avoided when not doing Java interop: the compiler cannot (or just isn't smart enough to) reason normally about which types are equal to which, because they are all gone...

To make the compiler understand that those types are the same, you need to give that type a name somehow.

A type parameter is a possibility: you define a parametrized method with the content of your for comprehension, so that within the method, the type is well-known.

def callHandler(url: String) {
  def call[T](r: Route[T]) = {
    val args = r.matcher.matchUrl(url)
    if (args.isDefined) r.handler(args.get)
    // or args.foreach(r.handler)
  }
  for (r <- routes) call(r)
  // or routes.foreach(call)
}

Note: in simpler cases, you could also use variance to have a List[Route[Any]], and your problem goes away, the type is well-known again. Here I am not sure you could make Route[A] covariant.

Existential types mostly exist to represent Java wildcards, Java raw types and the JVM's view of types (reflection and stuff), even though they are more powerful than those three constructs. If you can design things to avoid using them, you will save yourself a lot of pain. This was a bit controversial, but a least, there are a lot of limitations with the way they interact with type inference, so one has to be careful.

Community
  • 1
  • 1
gourlaysama
  • 10,828
  • 3
  • 41
  • 50
  • I understand the sentiment here, but it's just not true that Scala's existential types are _only_ for Java interoperability (Scala spends a whole keyword—and it doesn't have all that many—on `forSome`, for example). There are lots of completely legitimate (although sure, more advanced) uses that have nothing to do with Java. – Travis Brown Sep 13 '13 at 18:14
  • Yes, they are strictly more powerful that Java's wildcards and such (and much older), but I remember Martin Odersky saying somewhere that they wouldn't be in Scala if it were not for compatibility with erasure, wildcards and raw types. – gourlaysama Sep 13 '13 at 18:20
  • I don't understand existential types -- like the meaning of `[T <: and="" another="" but="" define="" foo="" forsome="" helper="" here="" i="" method.="" probably="" question="" subquestion.="" suggestion="" t="" thanks.="" the="" type="" updated="" use="" vs="" with="" your=""> – Rob N Sep 13 '13 at 18:51