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
Related
I am trying to get proper type with a typed function e.g
sealed trait Pet
case object Dog extends Pet
case object Cat extends Pet
case object Fish
def getPet[P >: Pet](pet: P) : P = {
pet match{
case Dog => Dog
case Cat => Cat
}
}
val d: Dog = getPet(Dog)//not compiled
any ideas ?
Here is a typeclass example
sealed trait Pet
case object Dog extends Pet
case object Cat extends Pet
case object Fish extends Pet
trait PetFactory[P <: Pet] {
def getPet(p: P): P
}
object PetFactory {
def getPet[P <: Pet](p: P)(implicit petFactory: PetFactory[P]): P = petFactory.getPet(p)
implicit val dogFactory: PetFactory[Dog.type] = (dog: Dog.type) => dog
implicit val catFactory: PetFactory[Cat.type] = (cat: Cat.type) => cat
implicit val fishFactory: PetFactory[Fish.type] = (fish: Fish.type) => fish
}
import PetFactory._
getPet(Dog)
getPet(Cat)
getPet(Fish)
which outputs
res0: Dog.type = Dog
res1: Cat.type = Cat
res2: Fish.type = Fish
Note that the type of singleton object O is O.type, for example
val fish: Fish.type = Fish
Given a concrete class Animal, how do I define a function that only takes a subclass of Animal?
In typical examples like this Animal is a trait so defining [A <: Animal] implies that you already pass in a subclass of Animal. However, in a scenario like below where Animal is concrete, can I exclude that as being an allowed type?
I'm working with existing generated code, and this is just a generalized example of the problem. Therefore the implication is that I can't make Animal (or the equivalent) into a trait.
See below for an example:
class Animal {
def name: String = "General Animal"
}
class Dog extends Animal {
override def name: String = "Dog"
}
// How do I limit A to be a subtype of Animal (excluding Animal itself)?
class SpecificAnimalContainer[A <: Animal](a: A) {
def specificAnimal: A = a
}
val dogContainer = new SpecificAnimalContainer[Dog](new Dog)
// I do not want this to be able to compile.
val animalContainer = new SpecificAnimalContainer[Animal](new Animal)
Using shapeless you can write:
import shapeless._
class SpecificAnimalContainer[A <: Animal](a: A)(implicit ev: A =:!= Animal) {
def specificAnimal: A = a
}
// val animalContainer = new SpecificAnimalContainer[Animal](new Animal)// doesn't compile
Otherwise you can implement similar type for implicit yourself.
Type constraint for type inequality in scala
Enforce type difference
How can I have a negation type in Scala?
It's a bit unclear what you're trying to achieve, but your problem looks exactly like a book example from Scala documentation at
https://docs.scala-lang.org/tour/upper-type-bounds.html
abstract class Pet extends Animal {}
class PetContainer[P <: Pet](p: P) {
def pet: P = p
}
class Lion extends Animal {
override def name: String = "Lion"
}
// val lionContainer = new PetContainer[Lion](new Lion)
// ^this would not compile
Hope this helps
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.
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
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