Scala match on generic type - scala

I am trying to do a match on a value based on its type, and there is a case where the expected type is a generic, like below:
def foo(bar: Matchable) = bar match
case (bar: Vector[String]) => println(bar.mkString(","))
However, I get the following warning at runtime:
the type test for Vector[String] cannot be checked at runtime
Why cannot it be checked and how could I overcome this?

Related

How to implement subtype resolution of typeclass in scala

I want to understand how to go about implementing the following use-case using typeclasses in Scala (or find out if it is even possible).
Given a sealed trait and a couple of concrete cases:
sealed trait Base
case class Impl1() extends Base
case class Impl2() extends Base
Given a typeclass operating on Base and an instance for each of the corresponding base implementations:
trait Processor[B <: Base]:
def process(b: B): String
given Processor[Impl1] with:
def process(b: Impl1): String = ??? // not important
given Processor[Impl2] with:
def process(b: Impl2): String = ??? // not important
Given a list of base objects:
val objects: List[Base] = ??? // whatever
Is it possible to implement a method that goes something like this?
val processed = objects.map(obj => process(obj))
def process[B <: Base](b: B)(using proc: Processor[B]) = proc.process(b)
When I try to naively implement the above as-such, the compiler complains that it can't find an implicit for Processor[Base], which I guess it makes sense, since in the context of the method call for process(obj), the obj val has the Base type.
What I would like to do is to let the compiler figure out the concrete type of obj, fetch the corresponding given instance for the concrete type and inject it into the process method. Is it even possible to do such a thing? Does it even make sense?
(Note - I've written my code in scala 3, but I'll gladly accept an answer in scala 2 syntax).
With a list of base objects you won't be able to do it, the list doesn't contain the real type at compile time and the compiler won't be able to find their respective type classes.
In scala 3 the typed list is the tuple, so you can use a tuple to have a typed list and obtain what you are expecting. The method you need is and adaptation of the one you can find with the same name in the scala 3 documentation and is simple as:
inline def summonAll[T <: Tuple](tup: T): List[String] =
inline tup match
case _: EmptyTuple => Nil
case tupl: (t *: ts) => summonInline[Processor[t]].process(tupl.head) :: summonAll[ts](tupl.tail)
and you can use it only passing the list of elements you want to obtain in a tuple in the expected type:
summonAll[(A, B)]
Here you can see the following full code running:
import scala.compiletime.{erasedValue, summonInline}
trait Processor[A]:
def process(t: A): String
object Processor:
def apply[T: Processor]: Processor[T] = summon[Processor[T]]
given Processor[String] with
def process(t: String): String = "im String: " + t
given Processor[Int] with
def process(t: Int): String = "im Int: " + t
inline def summonAll[T <: Tuple](tup: T): List[String] =
inline tup match
case _: EmptyTuple => Nil
case tupl: (t *: ts) => summonInline[Processor[t]].process(tupl.head) :: summonAll[ts](tupl.tail)
val t1 = ("hi", "bye", 44 )
val t2 = summonAll(t1)
println(t2) //List(im String: hi, im String: bye, im Int: 44)
With this particular typeclass you can easily create the Processor[Base] which the compiler asks for:
given Processor[Base] with:
def process(b: Base): String = b match
case b: Impl1 => process(b)
case b: Impl2 => process(b)
Using type class derivation you can also generate it automatically, but I am afraid it's going to be far more code! And if you enable warnings (which you should), the compiler should warn you about a non-exhaustive match if a new subclass is added anyway. So I'd use this manual version unless you have quite a lot of subclasses to handle in your actual case or they change often.
Note "this particular typeclass" it wouldn't work e.g. if process took more than 1 B parameter, or some parameters with more complex types containing B.

Type erasure, generics and existential types

I have a List of a parametric type, List[Field[_]], and I want something like this:
sealed trait FieldType[K] { val name: String }
sealed trait StringField extends FieldType[String]
case object publisher extends StringField { val name = "publisher" }
// ...
trait Field[K] {
val field: FieldType[K]
val value: K
}
case class Publisher(value: String) extends Field[String] { val field = publisher }
// ...
def get[K](l: List[Field[_]], key: FieldType[K]) : Option[K] =
l match {
case Nil => None
case (a:Field[K]) :: rest => Some(a.value)
case (a:Field[_]) :: rest => get(rest, key)
}
which doesn't work, because K is erased. I tried typetags, but I must confess I got lost. Any quick way to get what I want?
Yes, you can use type tags to get the compiler to generate the erased type information. According to the Scala documentation there are three ways to optain a tag.
By adding an implicit evidence parameter of type ClassTag, the compiler will generate the missing type information if it cannot find a suitable implicit value. Given the evidence, we can obtain the runtime class and compare it to the runtime class of the value of your Field.
def get[A](l: List[Field[_]])(implicit evidence: ClassTag[A]): Option[A] =
l match {
case Nil => None
case (a: Field[A]) :: rest if evidence.runtimeClass == a.value.getClass =>
Some(a.value)
case a :: rest =>
get[A](rest)
}
However, note that in this example, get[Int] will produce a ClassTag with the runtime class Int whereas value.getClass will return a java.lang.Integer
Given case class Other(value: Int) extends Field[Int], this will yield the following result:
def main(args: Array[String]): Unit = {
println(get[String](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // Some(foo)
println(get[Integer](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // Some(4)
println(get[Int](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // None
}
Note that I removed the parameter key, as it did not serve a purpose. If the key is supposed to be unique, then, instead of checking the type tag, I would suggest matching against the key, thereby not using reflection:
def get[A](l: List[Field[_]], key: FieldType[A]): Option[A] = l match {
case Nil => None
case a :: rest if a.field == key => Some(a.value.asInstanceOf[A])
case a :: rest => get(rest, key)
}
Edit: To address your questions from the comments:
is the asInstanceOf necessary? ... I was under the impression that having to resort to it is sort of bad practice/to be avoided/unsafe. Is that correct?
Since l is of type List[Field[_]], a is of type Field[_], meaning that a.value has the existential type, i.e., the compiler does not know it is of type A. However, because of the relation between a.key and a.value, we know that if a.field is of type FieldType[A], then value is of type A, so this particular type cast is safe (as long as you do not change the code).
You are absolutely correct that having to use a type cast indicates a flaw in the design, so maybe the best solution is to redesign Field, or the list l. Actually, I'd ask myself why you need to put various different types of Field in a list and then extract a certain type of Field later on? Maybe List is the wrong data structure? Would a Map[FieldType[_], List[Field[_]] be a better choice to store your fields? Or a Map[Class[_], List[Field[_]]? Or maybe a custom data structure?
Note that you can get rid of the asInstanceOf in the following way:
def get[A](l: List[Field[_]], key: FieldType[A]): Option[A] = l match {
case Nil => None
case (a: Field[A]) :: rest if a.field == key => Some(a.value)
case a :: rest => get(rest, key)
}
but this does not give you more static safety, because type erasure makes Field[A] match against any generic version of Field. I'd say this variant is worse because it makes the explicit type cast implicit, hence this code is more error prone.
I was trying to use TypeTag[] and typeOf[] instead of ClassTag and runtimeClass, as those where the example I found on the documentation page you linked (and elsewhere). What's the difference? Most of all, I want information regarding the _ in Field[_], so why oh why the ClassTag is on A?!?
There are three type tags that were introduced as a replacement for Manifest:
TypeTag
WeakTypeTag
ClassTag
From the ScalaDoc:
ClassTags are a weaker special case of scala.reflect.api.TypeTags#TypeTags, in that they wrap only the runtime class of a given type, whereas a TypeTag contains all static type information.
While you could probably also solve the problem using TypeTag, I figured that it suffices to compare the runtime class.
The problem is that the existential type can really be any superclass of A, because the list l can contain all kinds of Fields. In the example I gave, we have a list of Field[Int] :: Field[Int] :: Field[String] :: Field[Int], hence the existential type in this case must be Any, the least common supertype of Int and String. In other words: You benefit nothing from deriving the existential type of l: List[Field[_]].
However, what you actually want to do is find the first element in the list whose value is of type A. Because A is erased, the only way to obtain information about its runtime type is by passing the information as an additional argument, e.g., using the implicit ClassTag evidence. Now all that remains is finding out which element has a value of the corresponding type, hence a.value.getClass == evidence.runtimeClass.

Scala - Map with default value None?

Given
trait A
case class B extends A
trait C
I'm trying to implement something akin to this:
val m = scala.collection.mutable.HashMap[String, C=>Option[A]](
... //some pre-defined mappings
).withDefaultValue((_:C) => None)
This will give me a type mismatch:
expected: C=>Option[A], actual: C=>None.type
I heard scala expects None to be returned from a catch clause, so I tried the very ugly
val m = scala.collection.mutable.HashMap[String, C=>Option[A]](
... //some pre-defined mappings
).withDefaultValue((_:C) => try {
throw new RuntimeException()
Some(B())
} catch{ case _ => None}
)
But that (thankfully) didn't work either:
type mismatch, expected: C=>Option[A], actual: C=>Option[B]
What's the proper way to do what I am trying to?
None is an instance of Option[Nothing]. But Option is covariant in its type parameter, and Nothing is a subtype of everything.
This means that None is a subtype of Option[A]. Just say so:
None: Option[A]
But type inference can usually do it for you. If you create the function in advance,
val default = (c: C) => None
then you'll get the wrong return type and need to specify it, but withDefaultValue should already know the type of your map.

scala : Match type argument for an object

if i have a class that accepts a Type argument for example Seq[T] , and i've many objects of this class. and i want to split them depending on type Argument T
for example :
val x = List(Seq[Int](1,2,3,4,5,6,7,8,9,0),Seq[String]("a","b","c"))
x.foreach { a =>
a match{
case _ : Seq[String] => print("String")
case _ : Seq[Int] => print("Int")
}
}
the result of this code is StringString.
it only matches the class Seq not the Type also , what should i do to force it to match the Type ?
What you're seeing happens due to Type Erasure (http://docs.oracle.com/javase/tutorial/java/generics/erasure.html), some IDEs can warn you for errors like these.
You could have a look at Manifests, for example check out What is a Manifest in Scala and when do you need it?
Edit: like Patryk said, TypeTag replaced Manifest in Scala 2.10, see Scala: What is a TypeTag and how do I use it?
TypeTag Approach
The Java runtime requires generic type param erasure. Scala compiler combats this by 'injecting' type info into methods declared with TypeTag type arg:
def typeAwareMethod[T: TypeTag] (someArg: T) {
... // logic referring to T, the type of varToCheck
}
(alternatively, can use an equivalent, more long-winded implicit param - not shown)
When scala compiles an invocation of a method having (1) type arg [T: TypeTag] and (2) normal arg someArg: T, it collects type param metadata for someArg from the calling context and augments the type arg T with this metadata. T's value plus tag data are externaly type-inferred from calls:
val slimesters = List[Reptile](new Frog(...), new CreatureFromBlackLagoon(...))
typeAwareMethod(slimesters)
Logic Referring to T (within above method) - runtime reflection
import scala.reflection.runtime.universe._ : contents of the universe object of type scala.relection.api.JavaUniverse. NB: subject to evolutionary API change
Direct TypeTag comparison:
The tag info can be obtained via method typeTag[T], then directly tested/pattern matched for (exact) equality with other type tags:
val tag: TypeTag[T] = typeTag[T]
if (typeTag[T] == typeTag[List[Reptile]]) ...
typeTag[T] match {
case typeTag[List[Reptile]] => ...
}
Limitations: not subtype aware (above won't match List[Frog]); no additional metadata obtainable through TypeTag.
Smarter Type-comparison operations:
Convert to Type via typeOf[T] (or typeTag[T].tpe). Then use the gammut of Type ops, including pattern-matching. NB: in reflection typespace, =:= means type equivalance (analogue of :), <:< means type conformance (analogue of <:)
val tType: Type = typeOf[T] // or equivalently, typeTag[T].tpe
if (typeOf[T] <:< typeOf[List[Reptile]]) ... // matches List[Frog]
typeOf[T] match {
case t if t <:< typeOf[List[Reptile]] => ...
}
// Running Example:
def testTypeMatch[T: TypeTag](t: T) = if (typeOf[T] <:< typeOf[Seq[Int]]) "yep!!!"
test(List[Int](1, 2, 3)) // prints yep!!!
Method still needs type param [T: TypeTag] or you'll get the type-erasure view of the world...
Introspect on Type metadata
I lied in 2 ;). For your case, typeOf[T] actually returns TypeRef (a subtype of Type), since you're instantiating a type declared elsewhere. To get at the full metadata, you need to convert Type to TypeRef.
typeTag[T].tpe match {
case t: TypeRef => ... // call t.args to access typeArgs (as List[Type])
case _ => throw IllegalArgumentException("Not a TypeRef")
}
instead of t: TypeRef, can extract parts via pattern match on:
case TypeRef(prefixType, typeSymbol, typeArgsListOfType) =>
Type has method:
def typeSymbol: Symbol
Symbol has methods:
def fullName: String
def name: Name
Name has methods:
def decoded: String // the scala name
def encoded: String // the java name
Solution For Your Case
Solution based on (3):
import scala.reflect.runtime.universe._
def typeArgsOf[T: TypeTag](a: T): List[Type] = typeOf[T] match {
case TypeRef(_, _, args) => args
case _ => Nil
}
val a = Seq[Int](1,2,3,4,5,6,7,8,9,0)
val b = Seq[String]("a","b","c")
// mkString & pring for debugging - parsing logic should use args, not strings!
print("[" + (typeArgsOf(a) mkString ",") + "]")
print("[" + (typeArgsOf(b) mkString ",") + "]")
Aside: there's an issue with this test case:
val x = List(Seq[Int](1,2,3,4,5,6,7,8,9,0),Seq[String]("a","b","c"))
Type of x is List[Seq[Any]]. Any is the lowest common ancestor of String and Int. In this case there's nothing to introspect, since all types descend from Any , and there's no further type information available. To get stronger typing, separate the two Seqs, either via separate variables or a tuple/pair - but once separated, no higher order common mapping / folding across the two. "Real world" cases shouldn't have this problem.
I would argue it's equally sensible to def the logic with multiple prototypes one per sequence type, than go into those type-erasure workarounds. The 2.10 compiler doesn't warn about type erasure, and at runtime it seems to work well in my case.
Presumably this avoids the problem, producing more intelligible code.

Scala pattern matching and type inference

Could someone explain why the following code compiles?
Option("foo") match {
case x: List[String] => println("A")
case _ => println("B")
}
This gives me an (expected) warning about type erasure, but it still compiles. I expected this to throw a type error, like it does if I matched on "foo" instead of Option("foo").
Thanks!
The code is commented, so let's take a moment to savor that:
/** If we can absolutely rule out a match we can fail early.
* This is the case if the scrutinee has no unresolved type arguments
* and is a "final type", meaning final + invariant in all type parameters.
*/
Notice that None is not final, for instance. I know, right?
If you ever try scalac -Ypatmat-debug, the comment here might help:
https://github.com/scala/scala/pull/650
Reachability is almost within reach:
https://issues.scala-lang.org/browse/SI-6146
But I don't see any promises about what might someday be warnable. For performance reasons? One could also say, why should it warn about an instanceOf[Foo[_]]?
For now, the spec sections 8.2 - 8.4 motivate why matching against Foo[a] is interesting (because of the bounds a acquires). I think I'll go read that again. After some coffee.
trait Foo[+A]
final class Fuzz[+A] extends Foo[A]
final object Fooz extends Foo[Nothing]
object Futz extends Foo[Nothing]
//error
Fooz match {
case x: List[_] => println("A")
case _ => println("B")
}
//no error
Futz match { ... }
I would assume that the compiler is treating both Option and List as Product, which is why it compiles. As you say, the warning about type erasure is expected. Here's an example that uses another Product:
scala> Option("foo") match {
| case x: Tuple2[String,String] => println("TUPLE")
| case x: List[String] => println("LIST")
| case _ => println("OTHER")
| }
<console>:9: warning: non variable type-argument String in type pattern (String, String) is unchecked since it is eliminated by erasure
case x: Tuple2[String,String] => println("TUPLE")
^
<console>:10: warning: non variable type-argument String in type pattern List[String] is unchecked since it is eliminated by erasure
case x: List[String] => println("LIST")
^
UPDATE w/r/t case classes (because of the comment below):
scala> case class Foo(bar: Int)
defined class Foo
scala> val y: Product = Foo(123)
y: Product = Foo(123)
I noticed that an error is shown when the class of the value you match is declared as final (and we know that String is final). I still don't know why there's no error without it.