Returning Set[Class[A]] over Set[Class[_]] - scala

On Scala 2.10.4, given the following trait and case classes:
scala> trait Parent
defined trait Parent
scala> case class Girl() extends Parent
defined class Girl
scala> case class Boy() extends Parent
defined class Boy
I'm trying to define a method, f, that produces a Set[Class[A]] where A's type is A <: Parent.
scala> def f[A <: Parent]: Set[Class[A]] = Set[Class[A]](classOf[Boy], classOf[Girl])
<console>:12: error: type mismatch;
found : Class[Boy](classOf[$Boy])
required: Class[A]
def f[A <: Parent]: Set[Class[A]] = Set[Class[A]](classOf[Boy], classOf[Girl])
^
<console>:12: error: type mismatch;
found : Class[Girl](classOf[$Girl])
required: Class[A]
def f[A <: Parent]: Set[Class[A]] = Set[Class[A]](classOf[Boy], classOf[Girl])
But, I can make it work if I use, what I believe is the "wildcard":
scala> def g[A <: Parent]: Set[Class[_]] = Set[Class[_]](classOf[Boy], classOf[Girl])
g: [A <: Parent]=> Set[Class[_]]
And it works:
scala> g
res5: Set[Class[_]] = Set(class Boy, class Girl)
Why did the first approach fail, but the second succeeded? Lastly, is there any risk (to type safety) using Class[_] in the above definition of g?

The problem is that Class[A] is invariant in its type parameter. So Class[Boy] is not a Class[Parent]. The compiler will warn you of this if you set an explicit return type of Set[Class[Parent]].
scala> def f[_ <: Parent]: Set[Class[Parent]] = Set(classOf[Boy], classOf[Girl])
<console>:24: error: type mismatch;
found : Class[Boy](classOf[$Boy])
required: Class[Parent]
Note: Boy <: Parent, but Java-defined class Class is invariant in type T.
You may wish to investigate a wildcard type such as `_ <: Parent`. (SLS 3.2.10)
def f[_ <: Parent]: Set[Class[Parent]] = Set(classOf[Boy], classOf[Girl])
The following method uses existential types, which essentially means you don't care what the type is. _ is a completely unbound, and the type parameter A is superfluous.
def g[A <: Parent]: Set[Class[_]] = Set[Class[_]](classOf[Boy], classOf[Girl])
You might as well write:
def g: Set[Class[_]] = Set[Class[_]](classOf[Boy], classOf[Girl])
Lastly, is there any risk (to type safety) using Class[_] in the above definition of g?
Type safety has pretty much gone out the window at this point, because _ can be anything. Boy, Girl, Parent, Any, Toaster, ...
I think the best you can hope for is to heed the warning and get something like this:
def g: Set[Class[_ <: Parent]] = Set(classOf[Boy], classOf[Girl])
This will at least ensure that you have a Set with elements bounded above by Parent. Exactly what you intend to do with it, I don't know.

Related

How to pass extential type of tuple to invariant generic function?

I have an extential type of tuple, and I want to pass it to a generic function (I use ClassTag in this example but it is a custom type class with invariant type parameter):
type TC = (ClassTag[T], Option[T]) forSome {type T}
def foo[T](ct: ClassTag[T], o: Option[T]) = {}
val tc: TC = (classTag[String], Option[String](null))
foo(tc._1, tc._2)
This gives me error:
error: type mismatch;
found : scala.reflect.ClassTag[T]
required: scala.reflect.ClassTag[Any]
Note: T <: Any, but trait ClassTag is invariant in type T.
You may wish to investigate a wildcard type such as _ <: Any. (SLS 3.2.10)
foo(tc._1, tc._2)
I wanna ensure the type of two parameters ct and o uses the same parameter type T and I thought extential type should ensure this, but it does not seem to work.
However if I don't use tuple and only use ClassTag, it works fine:
type TC = ClassTag[T] forSome {type T}
def foo[T](ct: ClassTag[T]) = {}
val tc: TC = classTag[String]
foo(tc)
So the previous error of ClassTag invariance does not make sense.
Why does it not work when I use a tuple? How can I make it work?
For val tc: TC = classTag[String], Option[String](null)) tc._1 has type ClassTag[_] (which is different from any ClassTag[T] e.g. ClassTag[Any] because ClassTag is invariant), tc._2 has type Option[_], which is the same as Option[Any] because Option is covariant
implicitly[Option[_] =:= Option[Any]]
implicitly[Option[Any] =:= Option[_]]
An existential type 𝑇 forSome { 𝑄 } where 𝑄 contains a clause type 𝑡[tps]>:𝐿<:𝑈 is equivalent to the type 𝑇′ forSome { 𝑄 } where 𝑇′ results from 𝑇 by replacing every covariant occurrence of 𝑡 in 𝑇 by 𝑈 and by replacing every contravariant occurrence of 𝑡 in 𝑇 by 𝐿.
https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#existential-types
So in
def foo[T](ct: ClassTag[T], o: Option[T]) = {}
foo(tc._1, tc._2)
we have the type mismatch. Any and _ (aka arbitrary T) are different types.
Try to use universal quantification rather than existential
type TC[T] = (ClassTag[T], Option[T])
def foo[T](ct: ClassTag[T], o: Option[T]) = {}
val tc: TC[String] = (classTag[String], Option[String](null))
foo(tc._1, tc._2)

Scala error: type mismatch; found : java.util.List[?0] required: java.util.List[B]

I have below Scala code.
This function defines type parameter type B which is subclass of A. It converts java.util.List[A] into java.util.List[B].
import java.util
import java.util.stream.Collectors
class Animal
class Dog extends Animal
class Cat extends Animal
object ObjectConversions extends App {
import java.util.{List => JList}
implicit def convertLowerBound[ B <: Animal] (a: JList[Animal]): JList[B] = a.stream().map(a => a.asInstanceOf[B]).collect(Collectors.toList())
val a= new util.ArrayList[Animal]()
a.add(new Cat)
convertLowerBound[Cat](a)
}
When I compile this program I get below error.
<console>:15: error: type mismatch;
found : java.util.List[?0]
required: java.util.List[B]
Note: ?0 >: B, but Java-defined trait List is invariant in type E.
You may wish to investigate a wildcard type such as `_ >: B`. (SLS 3.2.10)
implicit def convertLowerBound[ B <: Animal] (a: JList[Animal]): JList[B] = a.stream().map(a => a.asInstanceOf[B]).collect(Collectors.toList())
What is wrong with my program. How can I resolve this error
It looks like type erasure happened (from Animal to ?0) when using java.util.stream.Stream.map, regardless B or concrete type passed in, possibly due to incompatibility between Scala type inference and Java type inference.
When calling Java method and you still want generic type inference, you need to pass the generic type specifically:
def convertLowerBound[ B <: Animal] (a: JList[Animal]): JList[B] = a.stream().map[B](a => a.asInstanceOf[B]).collect(Collectors.toList[B]())
Then you can do your operation successfully:
scala> def convertLowerBound[ B <: Animal] (a: JList[Animal]): JList[B] = a.stream().map[B](a => a.asInstanceOf[B]).collect(Collectors.toList[B]())
convertLowerBound: [B <: Animal](a: java.util.List[Animal])java.util.List[B]
scala> convertLowerBound[Cat](a)
res30: java.util.List[Cat] = [Cat#6325af19, Dog#6ff6743f]
On the other hand, your conversion isn't really useful, because during runtime, all the generic type will be erased so List[A] or List[B] will be the same after compiling (they will become List of Object. You can take a look at the compiled bytecode). You can simply do direct casting on your List instead of on each element:
def convertLowerBound[B <: Animal : TypeTag] (a: JList[Animal]) = a.asInstanceOf[JList[B]]
Then you can do your use case successfully:
scala> a.add(new Cat())
res16: Boolean = true
scala> convertLowerBound[Cat](a)
res17: java.util.List[Cat] = [Cat#6325af19]
However when there is a type mismatch, e.g. you add a Dog in the List and try to cast it as a List of Cat, an error will occur, only when you try to access the element:
scala> a.add(new Dog())
res19: Boolean = true
scala> convertLowerBound[Cat](a)
res20: java.util.List[Cat] = [Cat#6325af19, Dog#6ff6743f]
scala> convertLowerBound[Cat](a).get(1)
java.lang.ClassCastException: Dog cannot be cast to Cat
... 28 elided

Implicit class resolution for parameterized types

In the following example, it seems that the Scala compiler only recognizes an implicit class when it is defined to take the higher-kinded representation of Wrapper. Why is that?
scala> case class Nested(n: Int)
defined class Nested
scala> case class Wrapper[A <: Product](nested: A)
defined class Wrapper
scala> implicit class I1[W <: Wrapper[A], A <: Product](underlying: W) {
| def ok1() = true
| }
defined class I1
scala> Wrapper(Nested(5)).ok1()
<console>:26: error: value ok1 is not a member of Wrapper[Nested]
Wrapper(Nested(5)).ok1()
^
scala> implicit class I2[W <: Wrapper[_]](underlying: W) {
| def ok2() = true
| }
defined class I2
scala> Wrapper(Nested(5)).ok2()
res1: Boolean = true
Is there a workaround for implicit resolution that maintains full information about the nested type, allowing typeclass evidence, e.g., TypeTag, to be attached to it?
Note: the example above shows Nested and Wrapper to be case classes but that's not integral to the question. It's simply a convenience for a shorter and simpler console session.
This is happening because of a limitation in Scala's type inference. See SI-2272.
The implicit fails to resolve because the compiler cannot properly infer A. We can see this if we enable -Xlog-implicits. Notice that A is inferred as Nothing:
I1 is not a valid implicit value for Test.w.type => ?{def ok: ?} because:
inferred type arguments [Wrapper[Nested],Nothing] do not conform to method I1's type parameter bounds [W <: Wrapper[A],A <: Product]
The same thing happens if we try to instantiate I1 manually:
scala> val w = Wrapper(Nested(5))
w: Wrapper[Nested] = Wrapper(Nested(5))
scala> new I1(w)
<console>:21: error: inferred type arguments [Wrapper[Nested],Nothing] do not conform to class I1's type parameter bounds [W <: Wrapper[A],A <: Product]
new I1(w)
^
<console>:21: error: type mismatch;
found : Wrapper[Nested]
required: W
new I1(w)
^
Now, the work-arounds.
First, Wrapper is a case class, so there shouldn't be a reason for it to have sub-types. You can remove the W type parameter, and change underlying to a Wrapper[A]:
implicit class I1[A <: Product](underlying: Wrapper[A]) {
def ok = true
}
If you still wish to require two type parameters, you can also require implicit evidence that W <:< Wrapper[A], while removing the upper-bound on the type parameter W:
implicit class I1[W, A <: Product](underlying: W)(implicit ev: W <:< Wrapper[A]) {
def ok = true
}
Everything Michael said is true. Here is some extra perspective on this issue.
Because of the way you wrote your implicit class it looks like you want the implicit class to work on all subtypes of Wrapper and have as specific information about all types involved as possible. (99% of the time it's a bad idea to extend case classes, but it is possible, and these tricks also work for non case classes).
The trick basically is to make sure that all the type parameters that you want inferred are present somewhere in the value parameter lists. Another thing to keep in mind is this:
scala> trait Foo[A]; trait Bar extends Foo[Int]
defined trait Foo
defined trait Bar
scala> implicitly[Bar with Foo[Int] =:= Bar]
res0: =:=[Bar with Foo[Int],Bar] = <function1>
Take these two pieces of knowledge and you can rewrite your implicit class like this:
implicit class I1[Y, A <: Product](underlying: Y with Wrapper[A]) {
def ok1(): (Y, A) = ???
}
And see it at work:
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class Nested(n: Int)
case class Wrapper[A <: Product](nested: A)
class Crazy(override val nested: Nested) extends Wrapper[Nested](nested)
implicit class I1[Y, A <: Product](underlying: Y with Wrapper[A]) {
def ok1(): (Y, A) = ???
}
// Exiting paste mode, now interpreting.
scala> :type Wrapper(Nested(5)).ok1()
(Wrapper[Nested], Nested)
scala> :type new Crazy(Nested(5)).ok1()
(Crazy, Nested)
Note that the last solution Michael gave is based on the same thing: by moving the upper bound to the implicit parameter list, A is now present in the value parameter lists and can be inferred by the compiler.

Scala F-Bounded Type Polymorphism

trait Account[T <: Account[T]]
case class BrokerAccount(total:BigDecimal) extends Account[BrokerAccount]
case class SavingsAccount(total:BigDecimal) extends Account[SavingsAccount]
Below function declaration and invocation works fine.
def foo1( xs: Array[T forSome { type T <: Account[T] }]):Array[T forSome { type T <: Account[T] }] = xs
foo1(Array(BrokerAccount(100),SavingsAccount(50)))
But below invocation gives compilation error.
def foo2( xs: List[T forSome { type T <: Account[T] }]):List[T forSome { type T <: Account[T] }] = xs
foo2(List(BrokerAccount(100),SavingsAccount(50)))
Error
Main.scala:14: error: type mismatch;
found : List[Product with Serializable with Main.Account[_ >: Main.SavingsAccount with Main.BrokerAccount <: Product with Serializable with Main.Account[_ >: Main.SavingsAccount with Main.BrokerAccount <: Product with Serializable]]]
required: List[T forSome { type T <: Main.Account[T] }] foo2(List(BrokerAccount(100),SavingsAccount(50)))
Can someone please explain me why compilation error occur in later case?
The key to the problem is variance - you're trying to return a contravariant value in covariant position (function return type).
Despite List type is covariant in its argument (trait List[+A]), this essentially means its values are contravariant (can be assigned to a List of supertypes):
val listOfSupers: List[_ >: Account[_]] = List(BrokerAccount(100), SavingsAccount(50))
What you're trying to return from the function foo2 is a complete contrary - List[_ <: Account[_]], thus the compiler error.
If instead of List you use Set there, which is invariant in its type parameter just like Array, everything will work fine.

Understanding Higher Kinded Type Function

Given Parent and Result Algebraic Data Types:
sealed trait Parent
case object Kid extends Parent
sealed trait Result[A <: Parent]
case class ResultImpl[A <: Parent](x: A) extends Result[A]
I then wrote:
def general[A <: Parent, F[_]](x: A, f: A => F[A]): F[A] =
f(x)
However, I'm not sure how to call general to get an output of type, ResultImpl[Kid.type].
I attempted:
scala> general(Kid, ResultImpl.apply)
<console>:19: error: inferred kinds of the type arguments (Kid.type,ResultImpl) do not conform to the expected kinds of the type parameters (type A,type F).
ResultImpl's type parameters do not match type F's expected parameters:
type A's bounds <: Parent are stricter than type _'s declared bounds >: Nothing <: Any
general(Kid, ResultImpl.apply)
^
<console>:19: error: type mismatch;
found : Kid.type
required: A
general(Kid, ResultImpl.apply)
^
<console>:19: error: type mismatch;
found : Nothing => ResultImpl[Nothing]
required: A => F[A]
general(Kid, ResultImpl.apply)
^
How can I do that with the general function?
You need to specify that your _ is <: Parent as well, because A is. Also you might want to split arguments into two groups for the compiler to infer A correctly:
def general[A <: Parent, F[_ <: Parent]](x: A)(f: A => F[A]): F[A] =
f(x)
Then the next works as expected:
general(Kid)(ResultImpl(_))