classtags in patmats

73 views
Skip to first unread message

Adriaan Moors

unread,
May 9, 2012, 7:13:09 AM5/9/12
to scala-i...@googlegroups.com, Martin Odersky, Eugene Burmako
If I understood correctly at the whiteboard yesterday, Martin suggested adding the following to ClassTag:

trait ClassTag[T] extends ArrayTag[T] with ErasureTag[T] with Equals with Serializable {


  def unapply(x: Any): Option[T] = if (erasure.isAssignableFrom(x.getClass)) Some(x.asInstanceOf[T]) else None


}


so that, instead of this:

scala> def foo[T](x: Any)(implicit T: ClassTag[T]) = x match { case x: T => println("found a T") case _ => println("no tea") }
warning: there were 1 unchecked warnings; re-run with -unchecked for details
foo: [T](x: Any)(implicit T: ClassTag[T])Unit

scala> foo[String](1)
found a T  // right, whatevs


you can do this:

scala> def foo[T](x: Any)(implicit T: ClassTag[T]) = x match { case T(x) => println("found a T: "+ x) case _ => println("no tea") }
foo: [T](x: Any)(implicit T: ClassTag[T])Unit

scala> foo[String]("a")
found a T: a

scala> foo[String](1)
no tea


part 2 of the story is to do this rewrite automatically during type checking 
(when we detect an uncheckable type test against T and a classtag CT: ClassTag[T] is available),

this always seems desirable (unless you like unchecked warnings) -- or does it?

the rewrite I propose (x: T is actually represented as x@(_ : T)) happens in two scenarios

1.  _ : T   ~~>  CT(_ : T)
(if : T is not checkable without the class tag CT, which must be available)

2.  Extractor(...) ~~> CT(Extractor(...))
(if Extractor.unapply(x: T): ... where T is not checkable... yadayada.. available)


finally, we would not emit an unchecked warning when the expected type of the type test is Any (as is the case when wrapped in the class tag extractor)

Miles Sabin

unread,
May 9, 2012, 7:39:27 AM5/9/12
to scala-i...@googlegroups.com, Martin Odersky, Eugene Burmako
On Wed, May 9, 2012 at 12:13 PM, Adriaan Moors <adriaa...@epfl.ch> wrote:
> If I understood correctly at the whiteboard yesterday, Martin suggested
> adding the following to ClassTag:
>
> trait ClassTag[T] extends ArrayTag[T] with ErasureTag[T] with Equals with Serializable
> {
>   def unapply(x: Any): Option[T] =
> if (erasure.isAssignableFrom(x.getClass)) Some(x.asInstanceOf[T]) else None
> }

Looks interesting, but I wonder if we can do better?

Suppose instead you allowed Extractor patterns to have explicit type
arguments, then without any other extensions to the language we could
have something like,

def foo[T : Typeable](x : Any) = x match {
case Typeable[T](t) => println("found a T")
case _ => println("no tea")
}

where extractor Typeable has an unapply which looks like,

object Typeable {
def unapply[T : Typeable](x : Any) = implicitly[Typeable[T]].cast(x)
}

and where trait Typeable looks like,

trait Typeable[T] {
def cast(x : Any) : Option[T]
}

The point being that we can make the dynamic type test implemented in
Typeable[T].cast a lot more precise than just,

if (erasure.isAssignableFrom(x.getClass))
Some(x.asInstanceOf[T])
else None

One possible implementation can be found in shapeless here,

https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala

Cheers,


Miles

--
Miles Sabin
tel: +44 7813 944 528
gtalk: mi...@milessabin.com
skype: milessabin
g+: http://www.milessabin.com
http://twitter.com/milessabin
http://underscoreconsulting.com
http://www.chuusai.com

Adriaan Moors

unread,
May 9, 2012, 8:07:04 AM5/9/12
to scala-i...@googlegroups.com, Martin Odersky, Eugene Burmako
Suppose instead you allowed Extractor patterns to have explicit type arguments,
yes, that's something I'd like to add during the 2.10.x cycle (both type and value arguments) 
 
The point being that we can make the dynamic type test implemented in
Typeable[T].cast a lot more precise than just,

 if (erasure.isAssignableFrom(x.getClass))
   Some(x.asInstanceOf[T])
 else None

One possible implementation can be found in shapeless here,
 https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala
 as far as I can see, these implicits construct Typeable instances from other Typeables, which is neat
(but how is it different from how we materialize ClassTags in the compiler? I think it also takes into account type tags we have lying around)

your cast seems to be the same as what I wrote

in other words, could you explain how my proposal should be adapted to accommodate your use case?
it seems to me they would work together quite naturally

Miles Sabin

unread,
May 9, 2012, 8:25:34 AM5/9/12
to scala-i...@googlegroups.com, Martin Odersky, Eugene Burmako
On Wed, May 9, 2012 at 1:07 PM, Adriaan Moors <adriaa...@epfl.ch> wrote:
>> Suppose instead you allowed Extractor patterns to have explicit
>> type arguments,
>
> yes, that's something I'd like to add during the 2.10.x cycle (both type and
> value arguments)

Cool :-)

>> The point being that we can make the dynamic type test implemented in
>> Typeable[T].cast a lot more precise than just,
>>
>>  if (erasure.isAssignableFrom(x.getClass))
>>    Some(x.asInstanceOf[T])
>>  else None
>>
>> One possible implementation can be found in shapeless here,
>>
>>  https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala
>
>  as far as I can see, these implicits construct Typeable instances from
> other Typeables, which is neat
> (but how is it different from how we materialize ClassTags in the compiler?
> I think it also takes into account type tags we have lying around)
>
> your cast seems to be the same as what I wrote

Well, not quite. Unless I misunderstood, you have the equivalent of,

def cast[T : ClassTag](x : Any) : Option[T] = {
val tag = implicitly[ClassTag[T]]
if(tag.erasure.isAssignableFrom(x.getClass))
Some(x.asInstanceOf[T])
else None
}

That's problematic in a lot of cases, eg.,

scala> cast[Int](23 : Any)
res9: Option[Int] = None

scala> val oops = cast[Option[String]](Option(23) : Any)
oops: Option[Option[String]] = Some(Some(23))

scala> oops.get.get
java.lang.ClassCastException: java.lang.Integer cannot be cast to
java.lang.String

On the other hand, the Typeable type class in shapeless is better behaved,

scala> import shapeless._ ; import Typeable._
import shapeless._
import Typeable._

scala> (23 : Any).cast[Int]
res0: Option[Int] = Some(23)

scala> (Option(23) : Any).cast[Option[String]]
res1: Option[Option[String]] = None


> in other words, could you explain how my proposal should be adapted to
> accommodate your use case?

The main thing is to have witnesses for element types pulled in recursively.

> it seems to me they would work together quite naturally

I do hope so :-)

Adriaan Moors

unread,
Jun 1, 2012, 12:51:46 PM6/1/12
to scala-i...@googlegroups.com, Martin Odersky, Eugene Burmako
FYI, here's my current approach to making unchecked (top-level) type tests into checked ones when we can (if we have a TypeTag):


(it's going to need more work next week, as the expression that creates the TypeTag that has the unapply method
may be a block, which then causes problems with symbols...  "a block in a pattern! what could possibly go wrong?")

Here's an example of it in action:

Welcome to Scala version 2.10.0-20120601-184008-1e756c2ac9 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_31).
Type in expressions to have them evaluated.
Type :help for more information.

scala>  def foo[T: TypeTag] = List(1) match { case x: T => 1 }
warning: there were 1 unchecked warnings; re-run with -unchecked for details
foo: [T](implicit evidence$1: TypeTag[T])Int

scala> foo[String] // a List(1) is not a String
scala.MatchError: List(1) (of class scala.collection.immutable.$colon$colon)
<...>

scala> foo[List[String]] // only test top-level type
res1: Int = 1

Eugene Burmako

unread,
Jun 1, 2012, 12:55:42 PM6/1/12
to adriaa...@epfl.ch, scala-i...@googlegroups.com, Martin Odersky
Maybe I'm missing something, but why are you using type tags, not class tags?

Adriaan Moors

unread,
Jun 1, 2012, 12:58:50 PM6/1/12
to Eugene Burmako, scala-i...@googlegroups.com, Martin Odersky
Right. A class tag is enough for our use case, since we only want to test erased types.

If I understand correctly, they would not be enough for Miles's casting magic, illustrated above
(it can check element types of collections by special-casing on the type constructor).

Eugene Burmako

unread,
Jun 1, 2012, 1:01:59 PM6/1/12
to adriaa...@epfl.ch, scala-i...@googlegroups.com, Martin Odersky
Can we have several modes of matching?

If there is nothing, we emit an unchecked warning.
If there is a classtag, we only check for erasure. 
If there is a typetag, we perform deep checks.

Adriaan Moors

unread,
Jun 1, 2012, 1:39:22 PM6/1/12
to Eugene Burmako, <scala-internals@googlegroups.com>, Martin Odersky
Sounds good to me. (3) is not going to happen immediately though. 
Reply all
Reply to author
Forward
0 new messages