How can I use a "match" statement to identify the value of a class variable? The following is invalid, and I can't find an acceptable variant -- other than if ... else if ... else ...
val c: Class[_] = classOf[Int]
val what = c match { case classOf[Int] => "int!"; case classOf[Float] => "float!" }
The compiler complains: error: not found: type classOf
And of course, I can't use Class[Int] because that type information is erased:
c match { case Class[Int] => "int!"; case Class[Float] => "float!" }
error: type Class of type Class does not take type parameters.
I've also tried variants like Int.class, all to no avail. (And I don't really want to convert to strings: I feel it's important to have the compiler catch renamed/moved classes.)
Am I being dense, or have I stumbled into a Scala blind spot?
The verbose case comparison works:
val what = c match {
case q if q == classOf[Int] => "int!"
case q if q == classOf[Float] => "float!"
}
Of course, being a lower-case identifier, classOf should not work directly in a case statement anyway. However, neither does an escaped
case `classOf`[Int]
work in this case, so you’ll have to go with the if-guard.
You can match on class values if you create a stable identifier (ie. a val) for them,
scala> val c: Class[_] = classOf[Int]
c: Class[_] = int
scala> val ClassOfInt = classOf[Int]
ClassOfInt: java.lang.Class[Int] = int
scala> val ClassOfFloat = classOf[Float]
ClassOfFloat: java.lang.Class[Float] = float
scala> val what = c match {
| case ClassOfInt => "int!"
| case ClassOfFloat => "float!"
| }
what: String = int!
Note that you can't match on type (ie. Class[Int]) because erasure means that the different type instantiations of Class[T] are indistinguishable at runtime ... hence the warning below
scala> val what = c match {
| case _: Class[Int] => "int!"
| case _: Class[Float] => "float!"
| }
warning: there were 2 unchecked warnings; re-run with -unchecked for details
what: java.lang.String = int!
I encountered the same problem and placing the class in a 'stable identifier' wasn't that practical. I found the next best thing was to have tidy 'else if' statements.
Using this method:
private def is[T <: AnyRef : Manifest](implicit cls: Class[_]) =
cls == manifest[T].runtimeClass
I can write:
implicit val arg = cls
if (is[ClassA]) ...
else if (is[ClassB]) ...
...
else throw new IllegalArgumentException("Unknown class: " + cls)
To consider inheritance:
val what = c match {
case q if classOf[Int].isAssignableFrom(q) => "int!"
case q if classOf[Float].isAssignableFrom(q) => "float!"
}
Related
I tried to do collection matching in Scala without using scala.reflect.ClassTag
case class Foo(name: String)
case class Bar(id: Int)
case class Items(items: Vector[AnyRef])
val foo = Vector(Foo("a"), Foo("b"), Foo("c"))
val bar = Vector(Bar(1), Bar(2), Bar(3))
val fc = Items(foo)
val bc = Items(bar)
we can not do this:
fc match {
case Items(x) if x.isInstanceOf[Vector[Foo]]
}
because:
Warning: non-variable type argument Foo in type scala.collection.immutable.Vector[Foo] (the underlying of Vector[Foo]) is unchecked since it is eliminated by erasure
and this:
fc match {
case Items(x: Vector[Foo]) =>
}
but we can do this:
fc match {
case Items(x#(_: Foo) +: _) => ...
case Items(x#(_: Bar) +: _) => ...
}
bc match {
case Items(x#(_: Foo) +: _) => ...
case Items(x#(_: Bar) +: _) => ...
}
As you can see, we are check - is collection Foo + vector or Bar + vector.
And here we are have some problems:
We can do Vector(Foo("1"), Bar(2)), and this is will be match with Foo.
We are still need "val result = x.asInstanceOf[Vector[Bar]]" class casting for result extraction
Is there are some more beautiful way?
Like this:
fc match {
case Items(x: Vector[Foo]) => // result is type of Vector[Foo] already
}
What you're doing here is fundamentally just kind of unpleasant, so I'm not sure making it possible to do it in a beautiful way is a good thing, but for what it's worth, Shapeless's TypeCase is a little nicer:
case class Foo(name: String)
case class Bar(id: Int)
case class Items(items: Vector[AnyRef])
val foo = Vector(Foo("a"), Foo("b"), Foo("c"))
val bar = Vector(Bar(1), Bar(2), Bar(3))
val fc = Items(foo)
val bc = Items(bar)
val FooVector = shapeless.TypeCase[Vector[Foo]]
val BarVector = shapeless.TypeCase[Vector[Bar]]
And then:
scala> fc match {
| case Items(FooVector(items)) => items
| case _ => Vector.empty
| }
res0: Vector[Foo] = Vector(Foo(a), Foo(b), Foo(c))
scala> bc match {
| case Items(FooVector(items)) => items
| case _ => Vector.empty
| }
res1: Vector[Foo] = Vector()
Note that while ClassTag instances can also be used in this way, they don't do what you want:
scala> val FooVector = implicitly[scala.reflect.ClassTag[Vector[Foo]]]
FooVector: scala.reflect.ClassTag[Vector[Foo]] = scala.collection.immutable.Vector
scala> fc match {
| case Items(FooVector(items)) => items
| case _ => Vector.empty
| }
res2: Vector[Foo] = Vector(Foo(a), Foo(b), Foo(c))
scala> bc match {
| case Items(FooVector(items)) => items
| case _ => Vector.empty
| }
res3: Vector[Foo] = Vector(Bar(1), Bar(2), Bar(3))
…which will of course throw ClassCastExceptions if you try to use res3.
This really isn't a nice thing to do, though—inspecting types at runtime undermines parametricity, makes your code less robust, etc. Type erasure is a good thing, and the only problem with type erasure on the JVM is that it's not more complete.
If you want something that is simple using implicit conversions. then try this!
implicit def VectorConversionI(items: Items): Vector[AnyRef] = items match { case x#Items(v) => v }
Example:
val fcVertor: Vector[AnyRef] = fc // Vector(Foo(a), Foo(b), Foo(c))
val bcVertor: Vector[AnyRef] = bc // Vector(Bar(1), Bar(2), Bar(3))
I have a match statement like this:
val x = y match {
case array: Array[Float] => call z
case array: Array[Double] => call z
case array: Array[BigDecimal] => call z
case array: Array[_] => show error
}
How do I simplify this to use only two case statements, since first three case statements do same thing, instead of four.
Type erasure does not really gives you opportunity to understand how array was typed. What you should do instead is to extract head ( first element) of array and check it's type. For example following code works for me:
List(1,2,3) match {
case (a:Int) :: tail => println("yep")
}
This work, although not very nice:
def x(y: Array[_]) = y match {
case a if a.isInstanceOf[Array[Double]] ||
a.isInstanceOf[Array[Float]] ||
a.isInstanceOf[Array[BigDecimal]] => "call z"
case _ => "show error"
}
Would have thought that pattern matching with "|" as below would do the trick. However, this gives pattern type is incompatible with expected type on Array[Float] and Array[BigDecimal]. It might be that matching of generic on this single case where it could work has not been given so much attention:
def x(y: Array[_ <: Any]) = y match {
case a # (_:Array[Double] | _:Array[Float] | _:Array[BigDecimal]) => "call z"
case a: Array[_] => "show error"
}
May be it helps a bit:
import reflect.runtime.universe._
object Tester {
def test[T: TypeTag](y: Array[T]) = y match {
case c: Array[_] if typeOf[T] <:< typeOf[AnyVal] => "hi"
case c: Array[_] => "oh"
}
}
scala> Tester.test(Array(1,2,3))
res0: String = hi
scala> Tester.test(Array(1.0,2.0,3.0))
res1: String = hi
scala> Tester.test(Array("a", "b", "c"))
res2: String = oh
You can obtain the class of array elements as follows (it will be null for non-array types): c.getClass.getComponentType. So you can write:
if (Set(classOf[Float], classOf[Double], classOf[BigDecimal]).contains(c.getClass.getComponentType)) {
// call z
} else {
// show error
}
Not particularly Scala'ish, though; I think #thoredge's answer is the best for that.
You could also check whether the Array is empty first and then if not, just pattern match on Array.head...something like:
def x(y: Array[_]) = {
y.isEmpty match {
case true => "error"
case false => y.head match {
case a:Double | a:BigInt => do whatever
case _ => "error"
}
}
}
In the scala console I can do the following without a problem :
scala> val tree = q"def f():MySuperType[(Char,Char)]"
tree: universe.DefDef = def f(): MySuperType[scala.Tuple2[Char, Char]]
scala> val q"def $f():$d" = tree
f: universe.TermName = f
d: universe.Tree = MySuperType[scala.Tuple2[Char, Char]]
scala> val tq"$a[$TheTypeThatIWant]" = d
a: universe.Tree = MySuperType
TheTypeThatIWant: universe.Tree = scala.Tuple2[Char, Char]
And I can get what I want : the content of TheTypeThatIWant
Now If I try to do that inside a quasiquote, I get a match exception and I didn't find a way to get the inner type of an applied type.
My code :
tree match {
case q"{..$body}" =>
body.foreach (_ match {
case q"def $functionName:$type = $osef" =>
val tq"$f[$typ]" = d //I want to get $typ !!
...
}
But all I get is :
exception during macro expansion:
exception during macro expansion:
scala.MatchError: MyMacro.MySuperType[(Char, Char)] (of class scala.reflect.internal.Trees$TypeTree)
at MyMacro$$anonfun$getBasicStructure$1$1.apply(MyMacro.scala:737)
at MyMacro$$anonfun$getBasicStructure$1$1.apply(MyMacro.scala:735)
at scala.collection.immutable.List.foreach(List.scala:383)
at MyMacro$.getBasicStructure$1(MyMacro.scala:735)
at MyMacro$.MyMacro_impl(MyMacro.scala:846)
How can I solve that ?
Thank you
Edit :
The problem is not only with quasiquotes, it bugs even when I work with Trees :
case Block(stats,expr) =>
stats.foreach(_ match {
case DefDef(_,_,_,_,typ,_) =>
typ match {
case AppliedTypeTree(t,args) => //doesnt go there
case TypeApply(t,args) => //doesnt go there
case x:TypeTree => //goes there but can't get any info about the applied type
case _ =>
}
})
Edit2 :
You have to do it that way :
case q"def $name:${d:TypeTree} = $b" =>
d.tpe match {
case TypeRef(x,y,z) => //z is the list of applied types, see scaladoc
case _ =>
}
Well, I guess that's because in the console, by the time you call val tq"$a[$TheTypeThatIWant]" = d , the type of d is actually known, but it's not the case in the macro.
If you have a pattern matching (case) in Scala, for example:
foo match {
case a: String => doSomething(a)
case f: Float => doSomethingElse(f)
case _ => ? // How does one determine what this was?
}
Is there a way to determine what type was actually caught in the catch-all?
case x => println(x.getClass)
Too easy :-)
Basically, you just need to bind the value in your catch-all statement to a name (x in this case), then you can use the standard getClass method to determine the type.
If you're trying to perform specific logic based on the type, you're probably doing it wrong. You could compose your match statements as partial functions if you need some 'default' cases that you don't want to define inline there. For instance:
scala> val defaultHandler: PartialFunction[Any, Unit] = {
| case x: String => println("String: " + x)
| }
defaultHandler: PartialFunction[Any,Unit] = <function1>
scala> val customHandler: PartialFunction[Any, Unit] = {
| case x: Int => println("Int: " + x)
| }
customHandler: PartialFunction[Any,Unit] = <function1>
scala> (customHandler orElse defaultHandler)("hey there")
String: hey there
foo match {
case a: String => doSomething(a)
case f: Float => doSomethingElse(f)
case x => println(x.getClass)
}
what is wrong in this piece of code?
(Left("aoeu")) match{case Right(x) => ; case Left(x) => }
<console>:6: error: constructor cannot be instantiated to expected type;
found : Right[A,B]
required: Left[java.lang.String,Nothing]
why the pattern matcher just doesn't skip the Right and examine Left?
Implicit typing is inferring that Left("aoeu") is a Left[String,Nothing]. You need to explicitly type it.
(Left("aoeu"): Either[String,String]) match{case Right(x) => ; case Left(x) => }
It seems that pattern matching candidates must always be of a type matching the value being matched.
scala> case class X(a: String)
defined class X
scala> case class Y(a: String)
defined class Y
scala> X("hi") match {
| case Y("hi") => ;
| case X("hi") => ;
| }
<console>:11: error: constructor cannot be instantiated to expected type;
found : Y
required: X
case Y("hi") => ;
^
Why does it behave like this? I suspect there is no good reason to attempt to match on an incompatible type. Attempting to do so is a sign that the developer is not writing what they really intend to. The compiler error helps to prevent bugs.
scala> val left: Either[String, String] = Left("foo")
left: Either[String,String] = Left(foo)
scala> left match {
| case Right(x) => "right " + x
| case Left(x) => "left " + x }
res3: java.lang.String = left foo