In Scala 2, what are possible ways to write a shortcut of a partial function without triggering unchecked warning? - scala

This is a follow-up question of:
In Scala 3: Why runtime pattern matching can't work reliably on duck type using JVM reflection?
I'm trying to create a pattern matching implementation in Scala that is more resilient to type erasure. It should still rely on only JVM reflection on class but can degrade smoothly to alternatives in case of a runtime sanity check or class cast error.
The signature of my implementation looks like this:
#unchecked
case class Match[T](self: T) {
def apply[R](
#unchecked cases: PartialFunction[T, R]*
): R = ???
}
When testing it on a case that involves type erasure:
trait H {
type Unknown <: String
Match("abc": Serializable).apply(
{ case v: Unknown => 1 },
{ case v: Any => 2 }
)
}
I still got the warnings:
[Warn] /xxx.scala:20:19: abstract type pattern Has.this.Unknown is unchecked since it is eliminated by erasure
[Warn] /xxx.scala:20:16: The outer reference in this type test cannot be checked at run time.
It appears that #unchecked annotation has no effect on suppressing compiler message. This effectively rules out possibility to use partial function in my definition.
How can this warning be suppressed?

Well, you just put #unchecked in wrong places. It's for annotating types (making a type AnnotatedType internally). Try to annotate the type in type pattern. Usage example is in the scaladoc of #unchecked and in Scala spec.
case class Match[T](self: T) {
def apply[R](
cases: PartialFunction[T, R]*
): R = ???
}
trait H {
type Unknown <: String
Match("abc": Serializable).apply(
{ case v: Unknown #unchecked => 1 },
{ case v: Any => 2 }
)
}
Scaladoc: https://www.scala-lang.org/api/2.13.10/scala/unchecked.html
Scala spec: https://www.scala-lang.org/files/archive/spec/2.13/11-annotations.html#scala-compiler-annotations
What is correct syntax for #unchecked in a fold operation
Scala: recursively pattern match a heterogeneous list, obtaining the correct type of each element
Scala: pattern matching on a path-dependent type

Related

dealing with extractors on case classes with an invariant type parameter

I think, my issue is best described by some sample code:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X { def f: Foo[_] }
case class Wrapper[D](f: Foo[D]) extends X
val w: X = Wrapper(new Bar)
w match { case Wrapper(_: Bar) => 1 }
The last line fails with
found : Bar
required: Foo[Any]
Note: String <: Any (and Bar <: Foo[String]), but class Foo is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
I understand that this happens because unapply is defined with a type parameter, which is inferred as Any, and so it complains about String being incompatible.
But the question is if there is any way to make it work? I tried giving a type parameter to the exctractor, like this: w match { case Wrapper[String](_: Bar) => 1 }, but it says it does not take parameters (which is a lie) ... :(
The only way I came up with so far is this ugly baby:
w match { case w: Wrapper[String] if w.f.isInstanceOf[Bar] => 1 }
or, maybe,
Option(w).map(_.f) match { case Some(_: Bar) => 1 }
(the latter works because Option is covariant, but I can't make my classes covariant unfortunately). Also, I can't really use the last alternative without some additional ugliness IRL, because the equivalent of X in real life doesn't actually have f.
Any better ideas?
Define custom extractor
Wrapper.unapply does take type parameter, but you cannot specify one* in a pattern match sequence, so compiler infers one for you (and if compiler does that, it's very often Any or Nothing).
And, actually, you don't want it to, because you're removing your type information when you coerce your element to type X. So, you want a matcher for existential version
object WrapperEx {
def unapply(w: Wrapper[_]): Option[Foo[_]] = Wrapper.unapply(w)
}
And use it like so:
w match { case WrapperEx(_: Bar) => 1 }
Runnable version here
Good news: you can delegate to generated case class matcher.
Bad news: you cannot define it inside case class companion. Scala is happily picking the wrong one already, so it won't be able to disambiguate.
Still, I'd say it's not half-bad
* you can in latest Typelevel Scala, but I'm not sure how it works with type casts and I could not get it to work for your case.
You can parameterize trait X to get rid of existential type in def f: Foo[_], I think this is what trips the compiler up. The following code works:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X[A] { def f: Foo[A] }
case class Wrapper[D](f: Foo[D]) extends X[D]
val w: X[String] = Wrapper(new Bar) // type ascription can be omitted and will be inferred
w match { case Wrapper(_: Bar) => 1 }

How to narrow (cast) down an erased type to an intersection type in scala?

Consider:
trait Base
trait Derived extends Base
class Narrow[T<:Base](arg :T) {
val d = arg match {
case derived :Derived => Some(derived)
case _ => None
}
}
In previous versions of scala (before 2.11.8) I remember derived to have the type of T with Derived. This sometimes caused unexpected behaviour of type inference, but provided full information. Now (in 2.11.8), derived within the case clause is of type simply Derived. This is of course a simplified example, but in reality I have a method accepting a T with Derived and I wonder if it is possible to safely (and without compiler warnings) obtain a reference to such an instance? Naturally,
case derived :Derived with T =>
produces warning about unchecked (erased) type matching.
case derived: Derived with T #unchecked works. Of course, using #unchecked means you must make sure the code is safe instead of the compiler, but in this case I think it's as safe as it was before 2.11.8 (i.e. thanks to type erasure clients can pass arguments which aren't actually T).
Since pattern match with generic will be erased by compiler in compile time and all match clauses will match the superclass: Derived
Maybe TypeTag is a better solution for this scenario:
class Narrow[T <: Base](arg: T)(implicit typeTag: TypeTag[T]) {
val d = arg match {
case derived if typeTag.tpe =:= typeOf[Derived] => println("I am ")
case derived if typeTag.tpe <:< typeOf[Derived] => println("Hello")
case _ => println("world")
}
}
only match type: Derived firstly, actual type: Derived matched without Derived subclass type
match Derived subclass type(that Derived with T or T extends Derived, narrow it), since Derived type has matched in the clause 1, this clause will not match Derived type now
match other classes.

Scala #specialized annotation on function doesn't generate specialized type in pattern matching

I have the following function in scala, I intend to use it to convert any traversable into array and wrap any non-traversable with array:
def asArray[#specialized(scala.Int, scala.Long, scala.Float, scala.Double/*, scala.AnyRef*/) T <: Any : ClassTag](obj: Any): Array[T] = obj match {
case v: TraversableOnce[T] => v.toArray
case v: Array[T] => v
case v: T => Array[T](v)
case _ => Array[T]()
}
The #specialized annotation was added since to circumvent https://issues.scala-lang.org/browse/SI-6967. Namely a bug in scala 2.10 where primitive types may fail pattern matching.
However, the compiler gave me the following errors:
Error:(167, 27) type mismatch;
found : Any
required: Double
case v: T => Array[T](v)
^
This is strange as v: T should be a double-type variable in the specialized implementation (instead of Any). Where did I do wrong?

Can I suppress the unchecked warning in this case?

I have (simplified from actual code):
class Def[T]
object Fun {
def unapply[A,B](d: Def[A => B]): Option[A => B] = ???
}
def isFun(d: Def[_]) = d match {
case Fun(f) => true
case _ => false
}
This produces a warning:
non-variable type argument A => B in type pattern TypeName.this.Def[A => B] is unchecked since it is eliminated by erasure
I've tried placing #unchecked after Fun(f), but this produces an error; and after f, which doesn't suppress the warning. Is there any way to remove this warning?
I hope I'm wrong, but after browsing the SLS, I don't believe you can apply the annotation in the right place without changing your code.
Since annotations "may apply to definitions or declarations, types, or expressions" (Chapter 11), you need one of those for your annotation application to be syntactically correct. The two most likely candidates here seem to be either a type or an expression. However, looking at Chapter 8 Pattern Matching, it seems
Fun(f)
i.e. the statement where you need to apply the annotation to, is neither, since it looks like it corresponds to:
StableId '(' varid ')'
none of which seem to fit the bill for either an expression or a type (or any other valid annotation target).
First, can you change the signature of def isFun(d: Def[_]) to def isFun[A,B](d: Def[A=>B])?
If not, the issue is the type erasure. On the JVM, you can't have this code :
trait Foo {
def doStuff(xs:List[Int])
def doStuff(xs:List[Long])
}
At the runtime, you don't have the info of the generics, their type is erased.
So, in your case, the problem is that you can't pattern match against a generic.
Let's see that example in a Scala Worksheet:
object Fun {
def unapply[A, B](xs: List[Int]): Option[Int] = Some(1)
}
def isFun(d: List[_]) = d match {
case Fun(f) => true
case _ => false
}
//> isFun: (d: List[_])Boolean
isFun(List(1.3))
//> res0: Boolean = true
The generic type that we wanted to pattern match against was Int, but it did work with Float.
So, I think that you should change your approach as this pattern matching is obviously going to be a problem.

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.