The Liskov Substitution Principle tells us that if A is a subtype of B than everything we can do with type B we should be able to do with type A.
So to investigate this further, I create the following:
class Animal
class Dog extends Animal
class BlueDog extends Dog
I understand why I am not allowed to do
val c: Array[Animal] = a
as Arrays are not covariant in Scala (like they are in Java).
But, I think I should be able to do:
val a: Array[Dog] = Array(new Dog())
val b: Array[BlueDog] = a
I would expect val b to be ok.
But I get:
class Array is invariant in type T. You may wish to investigate a wildcard type such as `_ >: ...
val a: Array[Dog] = Array(new Dog())
val b: Array[BlueDog] = a
Is a little strange, since your BlueDog is more strictly then Dog and may have other method.
class Animal
class Dog extends Animal
class BlueDog extends Dog {
def wolf() { println ("I'm a blue dog") }
}
Then what should the following code do?
val a: Array[Dog] = new Array(new Dog())
val b: Array[BlueDog] = a
b(0).wolf()
Well, your Dog in Array a does not have wolf() method....so it's clearly that you should not assign a parent type to subtype.
That's why the following works:
val dog: Dog = new BlueDog
But the following doesn't:
val blueDog: BlueDog = new Dog
Related
In a typical Scala upperbound example
abstract class Animal {
def name: String
}
abstract class Pet extends Animal {}
class Cat extends Pet {
override def name: String = "Cat"
}
class Dog extends Pet {
override def name: String = "Dog"
}
class Lion extends Animal {
override def name: String = "Lion"
}
What is the difference between this
class PetContainer[P <: Pet](p: P) {
def pet: P = p
}
val dogContainer = new PetContainer[Dog](new Dog)
val catContainer = new PetContainer[Cat](new Cat)
and this?
class PetContainer1(p: Pet) {
def pet: Pet = p
}
val dogContainer1 = new PetContainer1(new Dog)
val catContainer1 = new PetContainer1(new Cat)
What is the advantage of using an upper type bound vs using the abstract class/trait directly?
With upper bound you can have a collection of specific subtype - so limiting to only cats or dogs and you can get a specific subtype back from def pet. It's not true for PetContainer1.
Losing more accurate type info example:
val doggo: Dog = new Dog
val dogContainer1 = new PetContainer1(doggo)
// the following won't compile
// val getDoggoBack: Dog = dogContainer1.pet
val dogContainer2 = new PetContainer[Dog](doggo)
// that works
val getDoggoBack: Dog = dogContainer2.pet
You can't put a cat into dog container:
// this will not compile
new PetContainer[Dog](new Cat)
It would get more significant if you would be dealing with collections of multiple elements.
The main difference between the two is actually the difference between these two types:
def pet: P = p
// and
def pet: Pet = p
So, in the first example, the type of pet is P, and in the second example, the type of pet is Pet. In other words: in the first example, the type is more precise, since it is a specific subtype of Pet.
If I put a Dog in the PetContainer[Dog], I get a Dog back out. Whereas, if I put a Dog in the PetContainer1, I get back a Pet, which could be either a Cat or a Dog, or something else entirely. (You haven't made your classes sealed or final, so someone else could come along, and create their own subclass of Pet.)
If you use an editor or IDE which shows you the types of expressions, or you try your code in the REPL or in Scastie, you actually see the difference:
dogContainer.pet // has type `Dog`
catContainer.pet // has type `Cat`
dogContainer1.pet // has type `Pet`
catContainer1.pet // has type `Pet`
See Scastie link for an example:
sealed trait Animal:
val name: String
sealed trait Pet extends Animal
case object Cat extends Pet:
override val name = "Cat"
case object Dog extends Pet:
override val name = "Dog"
case object Lion extends Animal:
override val name = "Lion"
final case class PetContainer[+P <: Pet](pet: P)
val dogContainer = PetContainer(Dog)
val catContainer = PetContainer(Cat)
final case class PetContainer1(pet: Pet)
val dogContainer1 = PetContainer1(Dog)
val catContainer1 = PetContainer1(Cat)
dogContainer.pet //=> Dog: Dog
catContainer.pet //=> Cat: Cat
dogContainer1.pet //=> Dog: Pet
catContainer1.pet //=> Cat: Pet
This gets more interesting if, say, you have a kennel that can hold two pets:
sealed trait Pet:
val name: String
case object Cat extends Pet:
override val name = "Cat"
case object Mouse extends Pet:
override val name = "Mouse"
final case class UnsafePetKennel(pet1: Pet, pet2: Pet)
val unsafeCatKennel = UnsafePetKennel(Cat, Cat)
val unsafeMouseKennel = UnsafePetKennel(Mouse, Mouse)
val oopsSomeoneAteMyMouse = UnsafePetKennel(Cat, Mouse)
final case class SafePetKennel[+P <: Pet](pet1: P, pet2: P)
val safeCatKennel = SafePetKennel[Cat.type](Cat, Cat)
val safeMouseKennel = SafePetKennel[Mouse.type](Mouse, Mouse)
val mouseIsSafe = SafePetKennel[Cat.type](Cat, Mouse)
// Type error: found `Mouse`, required `Cat`
val mouseStillSafe = SafePetKennel[Mouse.type](Cat, Mouse)
// Type error: found `Cat`, required `Mouse`
Scastie link
Unfortunately, things can still go wrong if we rely on Scala's type parameter inference:
val inferredKennel = SafePetKennel(Cat, Mouse)
works, because Scala infers P to be the least upper bound of Cat and Mouse, which is Pet, and thus inferredKennel has type SafePetKennel[Pet], and since both Cat and Mouse are subtypes of Pet, this is allowed.
We can fix that with a slight modification to our SafePetKennel:
final case class SaferKennel[+P <: Pet, +Q <: Pet](
pet1: P, pet2: Q)(implicit ev: P =:= Q)
val inferredKennel = SaferKennel(Cat, Mouse)
// Cannot prove that Cat =:= Mouse.
Scastie link
I've started to read about scala generics. Who can explain me why whit code work?
sealed abstract class Animal
class Cat extends Animal
class Dog extends Animal
class Box[A >: Animal] {
def set(a: A): A = ???
}
val catBox: Box[Animal] = new Box[Animal]
val dog = new Dog
catBox.set(dog)
I am guessing here, that what you mean by "does not work" is that you did not expect to be able to set a Dog into your catBox, because Dog is not a superclass of Animal.
This is expected though. Your definition of Box[Animal].set becomes
def set(a: Animal): Animal. Now, a Dog is an Animal, so, it satisfies the definition.
I don't quite understand what your intent is here. The type bound on Box restricts what kinds of boxes you can create:
new Box[Animal] // compiles
new Box[Dog] // does not compile - Dog is not a superclass of Animal
new Box[Any] // compiles - Any is a superclass of everything
but why would you want to restrict it like this doesn't make very much sense.
Perhaps, you wanted the upper bound instead:
class AnimalBox[A <: Animal]
val animalBox = new AnimalBox[Animal] // compiles
val dogBox = new AnimalBox[Dog] // compiles: Dog is a subclass of Animal
val catBox = new AnimalBox[Cat] // compiles: Cat is a subclass of Animal
val badBox = new AnimalBox[Any] // does not compile: Any is not a subclass
animalBox.set(new Dog) // compiles: Dog is an Animal
animalBox.set(new Cat) // compiles: Cat is an Animal
animalBox.set(new Pear) // does not compile: Pear is not an Animal
dogBox.set(new Dog) // compiles
dogBox.set(new Cat) // does not compile: cat is not a dog
Operator ??? is equivalent to throw new NotImplementedError.
So, when you call catBox.set(dog), you are throwing an exception.
I am trying to create a List of classes, like this:
abstract class Animal {
def name = this.getClass.getName
def say: String
}
class Dog extends Animal {
def say = "Woof"
}
class Cat extends Animal {
def say = "Meow"
}
val animalClasses = List(Dog, Cat)
This falls over on the last line, with the error message:
Zoo.scala:18: error: not found: value Dog
Note that I could create a List of instances of classes easily with List(new Dog(), new Cat()), but that's not what I want.
There you go:
scala> List(classOf[Dog],classOf[Cat])
res1: List[Class[_ >: Cat with Dog <: Animal]] = List(class Dog, class Cat)
As per this:
The predefined function classOf[T] returns a runtime representation of the Scala class type T.
I have a covariant Scala type Thing[+B]. The implementation uses an internal mutable queue:
private val queue : AsyncQueue[B]()
AsyncQueue is a custom mutable queue implementation, with special properties which I can't easily implement in an immutable version. Because it's mutable, AsyncQueue is invariant. So I can't use it in my covariant type Thing.
Since queue is private, I can guarantee the correctness of my code: e.g. I won't try to assign queue to a reference of type Queue[Any]. How can I make this work, keeping Thing covariant in B, without using casts?
(The solution with casts is to declare an AsyncQueue[Object] and cast objects on enqueue/dequeue, which is very ugly.)
ETA: I understand type covariance, and I understand why I shouldn't be able to declare an AsyncQueue of a covariant type, or make AsyncQueue itself covariant. My question is how to design this code to avoid using casts everywhere.
You can make your member immune to the variance check by making it private[this], according to the spec.
scala> trait Thing[+A] { def next(): A }
defined trait Thing
expectedly,
scala> class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
<console>:12: error: covariant type A occurs in invariant position in type => scala.collection.mutable.ArrayBuffer[A] of value as
class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
but
scala> class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { private[this] val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
defined class Thingie
and
scala> class X
defined class X
scala> val xs = new Thingie[X]
xs: Thingie[X] = Thingie#49f5c307
scala> xs.next
res1: X = X#4816c290
You need #uncheckedVariance:
import scala.annotation.unchecked.uncheckedVariance
class A[T] {}
class B[+T] {
val a: A[T #uncheckedVariance] = null
}
Even the Scala standard library makes use of #uncheckedVariance, notably in allowing the invariant mutable collections to inherit from covariant traits.
If B is covariant in Thing[+B] then you will not be able to have B in a contravariant position in Thing, i.e.
def put(b:B) {...} // will fail to compile, can't use a covariant type in this position
But it is possible to make two interfaces for Thing, one where the type is used in covariant positions and one where it is used in contravarient positions, as in the following:
trait ThingProduce[+B] {
def get: B
}
trait ThingConsume[-B] {
def put(b: B)
}
class Thing[B] extends ThingConsume[B] with ThingProduce[B] {
private val queue = new scala.collection.mutable.Queue[B]
def put(b: B) {queue.enqueue(b)}
def get: B = queue.dequeue
def both(b: B): B = ???
}
So that with the class hierarchy:
class Animal
class Mammal extends Animal
class Dog extends Mammal
The following can be done:
val mammalThing: Thing[Mammal] = new Thing[Mammal]{}
val dogConsumer: ThingConsume[Dog] = mammalThing
val animalProducer: ThingProduce[Animal] = mammalThing
But not:
val dogConsumer: ThingConsume[Dog] = animalProducer
//or
val animalProducer: ThingProduce[Animal] = dogConsumer
Thus Thing[B] can be seen to be both covarient and contravariant, but only for certain members.
I was cobbling together an answer to this question: Scala mixin to class instance, where I showed a way of "mixing-in" another trait or class instance to an existing instance:
case class Person(name: String)
val dave = Person("Dave")
val joe = Person("Joe")
trait Dog { val dogName: String }
val spot = new Dog { val dogName = "Spot" }
implicit def daveHasDog(p: dave.type) = spot
dave.dogName //"Spot"
joe.dogName //error: value dogName is not a member of Person
So after the local implicit def, dave can effectively be used as a Person with Dog. My question is, if we wanted to define a method that takes a Person instance only where the Person has a Dog, how do we do it?
I can define a method such as
def showDog(pd: Person with Dog) = pd.name + " shows " + pd.dogName
however this is no good for dave since he is still just a Person, despite his implicit transformation abilities.
I tried defining
trait Dog [T] { val dogName: String }
val spot = new Dog [dave.type] { val dogName = "Spot" }
def showDog(p: Person)(implicit dog: Dog[p.type]) = ...
but this is not legal, giving error: illegal dependent method type. Any ideas?
If you compile with -Ydependent-method-types, your original code will work with this definition of showDog:
scala> def showDog(p: Person)(implicit ev: p.type => Dog) = p.name + " shows " + p.dogName
showDog: (p: Person)(implicit ev: p.type => Dog)java.lang.String
scala> showDog(dave)
res1: java.lang.String = Dave shows Spot