scalascala-2.8type-constraints

What do <:<, <%<, and =:= mean in Scala 2.8, and where are they documented?


I can see in the API docs for Predef that they're subclasses of a generic function type (From) => To, but that's all it says. Um, what? Maybe there's documentation somewhere, but search engines don't handle "names" like "<:<" very well, so I haven't been able to find it.

Follow-up question: when should I use these funky symbols/classes, and why?


Solution

  • These are called generalized type constraints. They allow you, from within a type-parameterized class or trait, to further constrain one of its type parameters. Here's an example:

    case class Foo[A](a:A) { // 'A' can be substituted with any type
        // getStringLength can only be used if this is a Foo[String]
        def getStringLength(implicit evidence: A =:= String) = a.length
    }
    

    The implicit argument evidence is supplied by the compiler, iff A is String. You can think of it as a proof that A is String--the argument itself isn't important, only knowing that it exists. [edit: well, technically it actually is important because it represents an implicit conversion from A to String, which is what allows you to call a.length and not have the compiler yell at you]

    Now I can use it like so:

    scala> Foo("blah").getStringLength
    res6: Int = 4
    

    But if I tried use it with a Foo containing something other than a String:

    scala> Foo(123).getStringLength
    <console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
    

    You can read that error as "could not find evidence that Int == String"... that's as it should be! getStringLength is imposing further restrictions on the type of A than what Foo in general requires; namely, you can only invoke getStringLength on a Foo[String]. This constraint is enforced at compile-time, which is cool!

    <:< and <%< work similarly, but with slight variations:

    This snippet by @retronym is a good explanation of how this sort of thing used to be accomplished and how generalized type constraints make it easier now.

    ADDENDUM

    To answer your follow-up question, admittedly the example I gave is pretty contrived and not obviously useful. But imagine using it to define something like a List.sumInts method, which adds up a list of integers. You don't want to allow this method to be invoked on any old List, just a List[Int]. However the List type constructor can't be so constrainted; you still want to be able to have lists of strings, foos, bars, and whatnots. So by placing a generalized type constraint on sumInts, you can ensure that just that method has an additional constraint that it can only be used on a List[Int]. Essentially you're writing special-case code for certain kinds of lists.