Let's have a hierarchy defined like this:
trait Upper
case class Error(msg: String) extends Upper
Trying to define a trait like this:
trait Mixin[T <: Upper] {
def compute[S <: T](param: String, obj: S)
def use = compute("hello", Error("world"))
}
Causes compilation error:
error: inferred type arguments [Error] do not conform to method compute's type parameter bounds [S <: T]
def use = compute("hello", Error("world"))
^
error: type mismatch;
found : Error
required: S
def use = compute("hello", Error("world"))
^
Can someone explain me why that happens, and how to workaround it?
Update
The type bounds can't be removed. They are necessary because of some implicits that won't work otherwise.
I'm not sure what you're trying to do with this, but the type parameter T on Mixin is an invariant with an upper-bound of Upper. That means that we know it is some sub-type of Upper, but we don't know which--which means we don't know that it has type Error. And we can't know that Error is a sub-type of T.
If you introduce a third type case class A extends Upper, it becomes more clear why this can't work.
T might be A, and if S <: A, then S = Error <: T = A is impossible.
How can you work around this? Remove the type bounds. It's not clear why you have them in the first place.
Related
I have a trait with a self-type annotation that has a type parameter. This trait is from a library and cannot be modified. I want to pass this trait to a function that will require an upper bound for the type parameter. For example, I have this code snippet:
sealed trait Job[K] { self =>
type T
}
case class Encoder[T <: Product]()
def encoder(job: Job[_])(implicit ev: job.T <:< Product): Encoder[job.T] =
new Encoder[job.T]()
This returns an error that Type argument job.T does not conform to upper bound Product and a warning that ev is never used. How should I design the encoder function?
Why it doesn't work?
Your issue has nothing to do with the generalized type constraint. You can remove it and still get the same error. A generalized type constraint is used to constrain the type of arguments the method can receive.
(implicit ev: job.T <:< Product) provides an evidence in scope that matches only if job.T <: Product, allowing only calls to the method with Job arguments where job.T <: Product. This is its purpose.
Your issue is because the Encoder class has its type parameter T <: Product. The generalized type constraint does not treat the type job.T itself as a subtype of Product, as you expected. The evidence only applies to value arguments, not to the type itself, because this is how implicit conversions work.
For example, assuming a value x of type job.T that can be passed to the method as an argument:
def encoder(job: Job[_])(x: job.T)(implicit ev: job.T <:< Product): Unit = {
val y: Product = x // expands to: ev.apply(x)
val z: Encoder[Product] = new Encoder[job.T] // does not compile
}
The first line compiles because x is expanded to ev.apply(x), but the second one cannot be expanded, regardless if Encoder is covariant or not.
First workaround
One workaround you can do is this:
def encoder[U <: Product](job: Job[_])(implicit ev: job.T <:< Product): Encoder[U] =
new Encoder[U]()
The problem with this is that while both type parameters U and T are subtypes of Product, this definition does not says much about the relation between them, and the compiler (and even Intellij) will not infer the correct resulting type, unless you specify it explicitly. For example:
val myjob = new Job[Int] {
type T = (Int, Int)
}
val myencoder: Encoder[Nothing] = encoder(myjob) // infers type Nothing
val myencoder2: Encoder[(Int, Int)] = encoder[(Int, Int)](myjob) // fix
But why use job.T <:< Product if we already have U <: Product. We can instead use the =:= evidence to make sure their types are equal.
def encoder[U <: Product](job: Job[_])(implicit ev: job.T =:= U): Encoder[U] =
new Encoder[U]()
Now the resulting type will be correctly inferred.
Second workaround
A shorter workaround is using a structural type instead:
def encoder(job: Job[_] { type T <: Product }): Encoder[job.T] =
new Encoder[job.T]()
Which is not only cleaner (doesn't require a generalized type constraint), but also avoids the earlier problem.
Both versions work on Scala 2.13.8.
Expanding on Alin's answer, you may also use a type alias to express the same thing like this:
type JobProduct[K, P <: Product] = Job[K] { type T = P }
// Here I personally prefer to use a type parameter rather than an existential
// since I have had troubles with those, but if you don't find issues you may just use
// JobProdut[_, P] instead and remove the K type parameter.
def encoder[K, P <: Product](job: JobProduct[K, P]): Encoder[P] =
new Encoder[P]()
This approach may be more readable to newcomers and allows reuse; however, is essentially the same as what Alin did.
I am starting to embrace abstract type members over type parameters - mainly because they seem to work better with type inference for me. However, I am still struggling to understand how to use them from outside of the types they are defined in. For example, I cannot understand why the following Scala program should not compile:
trait Thing
trait Describer {
type X<:Thing
def describe(x:X) = println(x)
}
object Program extends App {
def print[T <: Thing, D <: Describer]
(describer: D, thing:T)
(implicit ev: D#X =:= T)
= describer.describe(thing)
}
Intuitively, I would expect that the requirement D#X =:= T would guarantee that the two types are indeed equal and hence that instances of two could be used interchangeably, but I get this compilation error:
error: type mismatch;
found : thing.type (with underlying type T)
required: describer.X
= describer.describe(thing)
What have I misunderstood? Is there another way to do what I want to do? Or failing that, is it safe to cast thing to the required type (describer.X)?
The type of the parameter of describer.describe is actually describer.X and not D#X. Another thing you have to know is that A =:= B also functions as a conversion from A to B but (at least currently) not the other way around. So the following should work.
def print[T <: Thing]
(describer: Describer, thing: T)
(implicit ev: T =:= describer.X)
= describer.describe(thing)
I have the following code and will like to call a method on top of a class that implements the trait EventTraces[T] and at the same time T should implement the trait Event (e.g as in doSomethingOnTopOfAnEventTrace())
trait Event
class ConcreteEvent[T <: Event]
trait EventTrace[T <: Event]
class ConcreteEventTrace[T <: Event] extends EventTrace[T]
val concreteEventTrace : ConcreteEventTrace[ConcreteEvent] = new ConcreteEventTrace(new ConcreteEvent)
def doSomethingOnTopOfAnEventTrace[T <: Event, Z <: EventTrace[T]](eventTrace: Z) {
println("Action on top of a Any kind of EventTrace of any type of Event")
}
However calling doSomethingOnTopOfAnEventTrace(concreteEventTrace) gives me the following error:
Error:(129, 3) inferred type arguments [Nothing,ConcreteEventTrace[ConcreteEvent]] do not conform to method doSomethingOnTopOfAnEventTrace type parameter bounds [T <: Event,Z <: EventTrace[T]]
doSomethingOnTopOfAnEventTrace(concreteEventTrace)
^
Error:(129, 38) type mismatch;
found : ConcreteEventTrace[ConcreteEvent]
required: Z
doSomethingOnTopOfAnEventTrace(concreteEventTrace)
^
The issue is that Scala can't infer T here because it only has Z in the arguments (I don't see a reason it couldn't, in this case). The obvious way to fix it is doSomethingOnTopOfAnEventTrace[ConcreteEvent, ConcreteEventTrace[ConcreteEvent]](concreteEventTrace). Another, if you don't actually need to be generic in Z:
def doSomethingOnTopOfAnEventTrace[T <: Event](eventTrace: EventTrace[T])
You could also try to use type members instead of generics in Event and EventTrace.
Suppose I have:
class Bounded[A] {
type apply[C <: A] = C
}
This compiles:
implicitly[Bounded[Any]#apply[String] =:= String]
This fails:
type Str = Bounded[Any]#apply[String]
...with:
[error] /home/grant/Workspace/scunits/test/src/main/scala/Box.scala:37: type arguments[String] do not conform to type apply's type parameter bounds [C <: A]
[error] type Str = Bounded[Any]#apply[String]
[error] ^
I tried using abstract types instead of type parameters, with the same result. The only work-around I found was to instantiate the type. This compiles:
val boundedAny = new Bounded[Any]
type Str2 = boundedAny.apply[String]
Unfortunately I'm working with phantom types which don't have run time instances, often for performance reasons.
Why does Scala produce a compile error here? Is there a better work-around?
Thanks for any help.
Update: In addition to the workaround below, I needed a way to override types with abstract type bounds. I did this like so:
object Test {
class AbstractBounded[A] {
type apply[C <: A] <: A
class Workaround[C <: A] {
type go = apply[C]
}
}
class Bounded[A] extends AbstractBounded[A] {
type apply[C <: A] = C
}
type Str = Bounded[Any]#Workaround[String]#go
}
How about:
scala> class Bounded[A] { class i[C <: A]{ type apply = C}}
defined class Bounded
scala> type TTT = Bounded[Any]#i[String]#apply
defined type alias TTT
scala> implicitly[TTT =:= String]
res4: =:=[TTT,String] = <function1>
Scala forgot to lookup generic (or another "abstract" type) before binding parameter to type alias. Given that =:= works fine - seems like a bug for me. Maybe implicits are resolving on another compilation level or just before this check.
Given two objects which extend a class:
object PageDAO
extends SalatDAO[Page, Long](collection=Config.getMongoDB("db_development")("pages"))
object BookDAO
extends SalatDAO[Book, Long](collection=Config.getMongoDB("db_development")("books"))
I want to write a function which has the object as a parameter:
def find[ID](salatDAO:SalatDAO[Product,ID]) = salatDAO.find(MongoDBObject()).limit(10)
Like
scala> find[Long](PageDAO)
<console>:27: error: type mismatch;
found : PageDAO.type (with underlying type object PageDAO)
required: com.novus.salat.dao.SalatDAO[Product,Long]
Note: Page <: Product (and PageDAO.type <: com.novus.salat.dao.SalatDAO[Page,Long]), but class SalatDAO is invariant in type ObjectType.
You may wish to define ObjectType as +ObjectType instead. (SLS 4.5)
find[Long](PageDAO)
Is this possible?
Just follow the advice of the compiler. If you want SalatDAO[A, _] to be a subclass of SalatDAO[B, _] when A <: B (i.e., when A is a subclass of B), declare SalatDAO to be covariant in its first parameter:
trait SalatDAO[+A, B] // ...
^ <-- that plus does the trick
If you cannot change the variance annotation, you can use usage-site bounds, à la Java, as Eastsun proposed in the comments:
def find[P <: Product, I](salatDAO: SalatDAO[P,I]) = // ...