Matching types with parameters

Hello,

As we know, matching can’t see type parameters because of erasure:

Welcome to Scala 2.12.3 (OpenJDK 64-Bit Server VM, Java 1.8.0_141).
Type in expressions for evaluation. Or try :help.

scala> def m(a: AnyRef):String = a match { case strings: Iterable[String] => strings.mkString(" "); case _ => "Yo!" }
<console>:11: warning: non-variable type argument String in type pattern Iterable[String] (the underlying of Iterable[String]) is unchecked since it is eliminated by erasure
       def m(a: AnyRef):String = a match { case strings: Iterable[String] => strings.mkString(" "); case _ => "Yo!" }
                                                         ^
m: (a: AnyRef)String

But what does one do if we want to match only Iterable[T] for specific T? Add a guard that checks the type of every element? Create an extractor that takes TypeTags?

Best, Oliver

Iterable[T] for specific T sounds like a description of def m[T](iterable: Iterable[T]): String = iterable mkString " ".

I meant I have, for example, an AnyRef and I want to match whether it is
Iterable[String].

Ah, in that case I’d probably downcast, e.g.

dem m(anyRef: AnyRef): String =
  try anyRef.asInstanceOf[Iterable[String]].mkString(" ")
  catch { case _: ClassCastException => "Yo!" }

I think that’ll never return "Yo!", as long as anyRef is an Iterable.

That’s true:

You need to back up and ask yourself how you got into this situation. What is the actual problem you are trying to solve? Whether TypeTag provides a way out depends on that.

The trouble is that whether a value of type Iterable[T] for some T is of type Iterable[String] is quite a different question from that of whether every element is a String. For example, given Q, a strict subtype of String, if xs: Iterable[Q], then xs: Iterable[String], and every element is a String, but Q != String. You can go the other way, too: Iterable[Any]("hi").

Suppose that you took every element being String as implying that the Iterable itself is Iterable[String].

package mtwp

// The only purpose of this trait is to demonstrate the sound covariant GADT
// consequence of a successful match; that is, "what is the meaning of
// determining that an `Iterable` is an `Iterable[String]`?"
trait StringIterable extends Iterable[String]

object Funs {
  def isStrings[A](xs: Iterable[A]): Option[String <:< A] = xs match {
    // Once again, I emphasize that the only purpose of `StringIterable` is to
    // prove that the return type of `isStrings` makes sense.  And this case is
    // sound.
    case _: StringIterable =>
      Some(implicitly[String <:< A])
    // But this case is not, as we'll see shortly.
    case _ if xs forall (_.isInstanceOf[String]) =>
      Some(implicitly[String <:< String].asInstanceOf[String <:< A])
    case _ => None
  }

  val foo = "foo"
  val bar = "bar"

  // I could just pass an empty iterable, and it wouldn't be cheating. But just
  // for skepticism's sake, I won't pass an empty iterable.
  val explosion: Option[String <:< foo.type] =
    isStrings[foo.type](Seq(foo))

  val totallyAFoo: Option[foo.type] = explosion map (_(bar))

  // Of course an empty iterable lets you just prove utter foolishness.
  val unsafeStroerce: String <:< Nothing =
    isStrings[Nothing](Iterable.empty).get

  val magic: Option[ClassLoader] = Some(unsafeStroerce("hi!"))
}

////////

Welcome to Scala 2.12.3 (OpenJDK 64-Bit Server VM, Java 1.8.0_112).
Type in expressions for evaluation. Or try :help.

scala> mtwp.Funs.totallyAFoo
res0: Option[mtwp.Funs.foo.type] = Some(bar)

scala> mtwp.Funs.magic
res1: Option[ClassLoader] = Some(hi!)
1 Like

By the way, the unsoundness of conflating the two concepts has nothing to do with erasure; it would still be unsound had we followed the siren song and foundered upon the craggy bluffs of “reified generics”. So, as @SethTisue mentioned, regardless, it depends on what you want to do.

Thanks for the feedback.

My specs are as follows: accept an object of any type. If it is of a
particular type Store, or Iterable[Store], take some special action. For
any other type of object, take some default action.

I’m happy to accept as “Iterable[Store]” an Iterable whose elements are
all of type Store. I haven’t made up my mind regarding empty Iterables, but
maybe for now I can accept them as Iterable[Store].

I’m assuming I can do this by writing a generic “Iterable[T]” extractor
using type tags. I could also write an extractor hard-coded for the type
Store.

I was assuming this is a common case and there might be some library
support, but maybe that’s not the case.

One question to answer to refine your approach is: why do you have to take an AnyRef here? Depending on your answer, it may not even be meaningful to take Iterable[Store] in any other form than “iterable whose elements are all Stores”.

Are you sure?

consider:

scala> import reflect.runtime.universe._
import reflect.runtime.universe._

scala> def isIterableString[T](x: T)(implicit tag: TypeTag[T]) = tag.tpe <:< typeOf[Iterable[String]]
isIterableString: [T](x: T)(implicit tag: reflect.runtime.universe.TypeTag[T])Boolean

scala> isIterableString(List("foo"))
res0: Boolean = true

scala> isIterableString(List(1))
res1: Boolean = false

so far so good. but then:

scala> val x: AnyRef = List("foo")
x: AnyRef = List(foo)

scala> isIterableString(x)
res2: Boolean = false

doh!

or, is that result acceptable to you? if so, carry on.