Scala: Making concrete classes of trait with type member that has a representation type - scala

This is a follow-up to a previous question where I had a trait Garage with a type member CarType, which itself had a type member FuelType, and I needed a function refuel that could take an instance of CarType as the first argument and an instance of the first argument's FuelType as the second argument.
The answer, the two traits below, was to give Car a representation type C <: Car[C]. The problem that I now have is that I can't figure out how to define the type parameter on the concrete classes implementing Garage, e.g. ConcreteGarage below.
trait Fuel
trait Garage {
type CarType <: Car[CarType]
def cars: Seq[CarType]
def copy(cars: Seq[CarType]): Garage
def refuel(car: CarType, fuel: CarType#FuelType): Garage = copy(
cars.map {
case `car` => car.refuel(fuel)
case other => other
})
}
trait Car[C <: Car[C]] {
type FuelType <: Fuel
def fuel: FuelType
def copy(fuel: C#FuelType): C
def refuel(fuel: C#FuelType): C = copy(fuel)
}
class ConcreteGarage(val cars: Seq[ConcreteGarage#CarType]) extends Garage {
type CarType = Car[CarType] // Nope
//type CarType = Car[Any] // Nope
//type CarType = Car[Nothing] // Nope
//type CarType = Car[Car] // Nope
//type CarType <: Car[CarType] // Nope
def copy(cars: Seq[CarType]) = new ConcreteGarage(cars)
}

That's why I said beware of the ugliness of having to carry around another type parameter for the rest of your life :)
class ConcreteGarage[C <: Car[C]](val cars: Seq[C]) extends Garage {
type CarType = C
def copy(cars: Seq[C]) = new ConcreteGarage(cars)
}
Of course, its simpler if you have a particular garage.
case class Benzin(litres: Int) extends Fuel
case class Mustang(fuel: Benzin) extends Car[Mustang] {
type FuelType = Benzin
def copy(fuel: Benzin) = Mustang(fuel)
}
case class MustangGarage(cars: Seq[Mustang]) extends Garage {
type CarType = Mustang
def copy(cars: Seq[Mustang]) = MustangGarage(cars)
}
val m = Mustang(Benzin(0))
val g0 = MustangGarage(Seq(m))
val g1 = g0.refuel(m, Benzin(45))

Related

How to restrict generic trait in subtype

I have a trait Mutable[T] that describes objects that can be mutated to T using a Mutation object:
trait Mutable[T] {
def mutate(mutation: Mutation): T
}
class Mutation {
def perform[T <: Mutable[T]](mutable: T): T = mutable.mutate(this)
}
I also have two traits describing animals in general, as well as specifically mammals.
I would like to require that an Animal can mutate into another Animal, but a Mammal can only mutate into another Mammal. However, the following does not compile:
trait Animal extends Mutable[Animal]
trait Mammal extends Animal, Mutable[Mammal]
case class Fish() extends Animal {
override def mutate(mutation: Mutation): Animal = Fish()
}
// error: class Monkey cannot be instantiated since it has conflicting base types Mutable[Animal] and Mutable[Mammal]
case class Monkey() extends Mammal {
override def mutate(mutation: Mutation): Mammal = Human()
}
// error: class Human cannot be instantiated since it has conflicting base types Mutable[Animal] and Mutable[Mammal]
case class Human() extends Mammal {
override def mutate(mutation: Mutation): Mammal = Monkey()
}
I would like to use these types as follows:
val mutation = new Mutation()
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish)
val monkey: Mammal = Monkey()
val monkey2: Mammal = mutation.perform(monkey)
Don't you want to make Mutable covariant?
trait Mutable[+T] {
def mutate(mutation: Mutation): T
}
In such case your code seems to compile in Scala 3
https://scastie.scala-lang.org/qVMDsu7HRLiBFlSchGxWEA
When you were loosening the restriction on Mutation.mutate to [T <: Mutable[? <: T]] Mutable[? <: T] is actually defining covariance at a call site
In Scala3, if generic type argument(s) is mapped to dependent type, how are covariant & contravariant modifiers mapped?
Also you can try to make T a type member rather than type parameter. In such case the existential type is just Mutable while a specific type is Mutable { type T = ... } (aka Mutable.Aux[...])
trait Mutable:
type T
def mutate(mutation: Mutation): T
object Mutable:
type Aux[_T] = Mutable { type T = _T }
class Mutation:
def perform[M <: Mutable](mutable: Mutable): mutable.T = mutable.mutate(this)
trait Animal extends Mutable:
type T <: Animal
trait Mammal extends Animal:
type T <: Mammal
case class Fish() extends Animal:
type T = Animal
override def mutate(mutation: Mutation): Animal = Fish()
case class Monkey() extends Mammal:
type T = Mammal
override def mutate(mutation: Mutation): Mammal = Human()
case class Human() extends Mammal:
type T = Mammal
override def mutate(mutation: Mutation): Mammal = Monkey()
val mutation = new Mutation()
val monkey: Mammal = Monkey()
val monkey2: Mammal = mutation.perform(monkey)
val monkey3: Mammal = mutation.perform[Mammal](monkey)
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish)
val fish3: Animal = mutation.perform[Animal](fish)
Bind wildcard type argument in Scala (answer)
Also you can try a type class
// type class
trait Mutable[T]:
type Out
def mutate(t: T, mutation: Mutation): Out
class Mutation:
def perform[T](t: T)(using mutable: Mutable[T]): mutable.Out = mutable.mutate(t, this)
trait Animal
trait Mammal extends Animal
case class Fish() extends Animal
object Fish:
given Mutable[Fish] with
type Out = Fish
def mutate(t: Fish, mutation: Mutation): Out = Fish()
case class Monkey() extends Mammal
object Monkey:
given Mutable[Monkey] with
type Out = Human
def mutate(t: Monkey, mutation: Mutation): Out = Human()
case class Human() extends Mammal
object Human:
given Mutable[Human] with
type Out = Monkey
def mutate(t: Human, mutation: Mutation): Out = Monkey()
val mutation = new Mutation()
val monkey: Monkey = Monkey()
val monkey2: Human = mutation.perform(monkey)
val monkey3: Human = mutation.perform[Monkey](monkey)
val fish: Fish = Fish()
val fish2: Fish = mutation.perform(fish)
val fish3: Fish = mutation.perform[Fish](fish)
Although type class instances have to be resolved statically while you seem to prefer resolving values dynamically (val fish: Animal = Fish, val monkey: Mammal = Monkey).
https://docs.scala-lang.org/tutorials/FAQ/index.html#how-can-a-method-in-a-superclass-return-a-value-of-the-current-type
http://tpolecat.github.io/2015/04/29/f-bounds.html
Advantages of F-bounded polymorphism over typeclass for return-current-type problem
The error is caused by a conflict between Mutable[Animal] and Mutable[Mammal] in the list of supertypes for Monkey.
To resolve the conflict, you can instead use Mutable[? <: Animal] which is compatible with Mutable[Mammal]:
trait Animal extends Mutable[? <: Animal]
trait Mammal extends Animal, Mutable[Mammal]
Then, to extend the type Animal, you have to explicitly add Mutable[Animal] as a supertype to be able to override the mutate method:
case class Fish() extends Animal, Mutable[Animal] {
override def mutate(mutation: Mutation): Animal = Fish()
}
It might be helpful to define a helper trait to save a bit of typing:
trait AnimalBase extends Animal, Mutable[Animal]
case class Fish() extends AnimalBase {
override def mutate(mutation: Mutation): Animal = Fish()
}
However, the type of Animal now no longer matches the requirement for Mutation.perform, even if you explicitly specify the type parameter:
val mutation = new Mutation()
val monkey: Mammal = Monkey()
val monkey2: Mammal = mutation.perform(monkey) // OK
val monkey3: Mammal = mutation.perform[Mammal](monkey) // OK
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish) // error: Found: (fish : Animal), Required: Nothing
val fish3: Animal = mutation.perform[Animal](fish) // error: Type argument Animal does not conform to upper bound Mutable[Animal]
This can be fixed by loosening the restriction on Mutation.mutate to [T <: Mutable[? <: T]]:
class Mutation {
def perform[T <: Mutable[? <: T]](mutable: T): T = mutable.mutate(this)
}
val mutation = new Mutation()
val fish: Animal = Fish()
val fish2: Animal = mutation.perform(fish) // OK
val fish3: Animal = mutation.perform[Animal](fish) // OK

Type mismatching when using type class

I defined a type class like this:
/** Type class */
trait Drivable[V <: Vehicle]{
def drive(v:V)
}
It request a subtype of Vehicle as parameter of drive function. And now I'm trying to use this type class to the Vehicle itself.
/** ADT */
sealed trait Vehicle{
def drive[V <: Vehicle](implicit d: Drivable[V]): Unit = d.drive(this)
}
the compiler complains:
Error:(15, 74) type mismatch;
found : Vehicle.this.type (with underlying type Vehicle)
required: V
def drive[V <: Vehicle](implicit d: Drivable[V]): Unit = d.drive(this)
It works if I force cast this to V
def drive[V <: Vehicle](implicit d: Drivable[V]): Unit = d.drive(this.asInstanceOf[V])
But I hate doing so. Or, it also works if I just put it in the case class:
final case class Car(name:String) extends Vehicle {
def drive(implicit d: Drivable[Car]): Unit = d.drive(this)
}
But imaging I have many case classes, I have to repeat this in everyone.
What's the correct to use this type class in the base trait?
Either make drive an extension method
sealed trait Vehicle
implicit class DrivableOps[V <: Vehicle](val vehicle: V) extends AnyVal {
def drive(implicit d: Drivable[V]): Unit = d.drive(vehicle)
}
or make V a type member of the trait rather than type parameter of the method
sealed trait Vehicle {
type V >: this.type <: Vehicle
def drive(implicit d: Drivable[V]): Unit = d.drive(this)
}
case class Bus() extends Vehicle {
override type V = Bus
}
case class Car() extends Vehicle {
override type V = Car
}
Type V can be generated automatically:
//libraryDependencies += "com.github.dmytromitin" %% "auxify-macros" % "0.8"
import com.github.dmytromitin.auxify.macros.self
#self sealed trait Vehicle {
def drive(implicit d: Drivable[Self]): Unit = d.drive(this)
}
#self case class Bus() extends Vehicle
#self case class Car() extends Vehicle

Aux-like pattern for path dependent types?

Goal: I would like to write
feedImplicitInstance[Cat](new CatFood())`
and have the same effect as
feedExplicitInstance(Cat.CatInstance)(new CatFood())
How can I do that ?
Is it possible to do that at all ?
This is what I tried (but it did not really work):
object DepType extends App{
println("bla")
def feedExplicitInstance[AnimalInstance]
(animal:AnimalTypeClass[AnimalInstance])(food:animal.FoodThatAnimalLikes) = {
animal.feed(food)
}
// Does not compile:
def feedImplicitInstance[AnimalInstance,Food](food:Food)
(implicit animal:AnimalTypeClass[AnimalInstance],aux:Cat.Aux[Food,AnimalInstance]) = {
animal.feed(food)
// Error:(17, 17) type mismatch;
// found : food.type (with underlying type Food)
// required: animal.FoodThatAnimalLikes
// animal.feed(food)
}
feedExplicitInstance(Cat.CatInstance)(new CatFood())
}
trait Food{
def eat():Unit
}
trait AnimalTypeClass [AnimalInstance] {
type FoodThatAnimalLikes <: Food
def feed(f:FoodThatAnimalLikes)=f.eat()
}
trait Cat
class CatFood extends Food{
override def eat(): Unit = println("meow")
}
object Cat {
type Aux[Food,Animal]= AnimalTypeClass[Animal] {type FoodThatAnimalLikes = Food}
implicit object CatInstance extends AnimalTypeClass[Cat]{
override type FoodThatAnimalLikes = CatFood
}
}
First of all, it makes more sense to put Aux in the AnimalTypeClass companion object, and switch the order of its type parameters. Though none of this is required to make it compile.
In order to enable your preferred calling convention of feedImplicitInstance[Cat](new CatFood()) feedImplicitInstance is allowed to have only one type parameter. But Food must be a type parameter because forward references like animal.FoodThatAnimalLikes in parameter lists are not allowed, as you probably noticed yourself. That's why you needed the Aux in the first place. To reconcile those conflicting constraints, you should manually implement a sort of type parameter currying. That's what the Feeder class in the following complete example is for:
object DepType extends App {
def feedExplicitInstance[AnimalInstance]
(animal: AnimalTypeClass[AnimalInstance])(food: animal.FoodThatAnimalLikes) = {
animal.feed(food)
}
class Feeder[AnimalInstance] {
def apply[F <: Food](food: F)(implicit animal: AnimalTypeClass.Aux[AnimalInstance, F]) =
animal.feed(food)
}
def feedImplicitInstance[AnimalInstance] = new Feeder[AnimalInstance]
feedExplicitInstance(Cat.CatInstance)(new CatFood())
feedImplicitInstance[Cat](new CatFood())
}
trait Food{
def eat(): Unit
}
class CatFood extends Food{
override def eat(): Unit = println("meow")
}
trait AnimalTypeClass[AnimalInstance] {
type FoodThatAnimalLikes <: Food
def feed(f: FoodThatAnimalLikes) = f.eat()
}
object AnimalTypeClass {
type Aux[A, F <: Food]= AnimalTypeClass[A] {type FoodThatAnimalLikes = F}
}
trait Cat
object Cat {
implicit object CatInstance extends AnimalTypeClass[Cat]{
override type FoodThatAnimalLikes = CatFood
}
}
If we define Aux like this:
object AnimalTypeClass {
type Aux[A, F] = AnimalTypeClass[A] { type FoodThatAnimalLikes = F }
implicit object CatInstance extends AnimalTypeClass[Cat] {
override type FoodThatAnimalLikes = CatFood
}
}
We can then summon the right implicit typeclass via a method:
def feed[A, F <: Food](f: F)(implicit animalTypeClass: AnimalTypeClass.Aux[A, F]) = {
animalTypeClass.feed(f)
}
And now this compiles and runs:
feed(new CatFood())
I changed the name of the generic type parameters a bit, but they're largely the same as in your example. Just note the implicit instance definition changes.
Using Yuval's answer, this is how my modified code looks like:
object DepType2 extends App{
println("bla")
def feedExplicitInstance[AnimalInstance]
(animal:AnimalTypeClass[AnimalInstance])(food:animal.FoodThatAnimalLikes) = {
animal.feed(food)
}
def feedImplicitInstance[AnimalInstance,Food](food:Food)
(implicit animal:AnimalTypeClass[AnimalInstance],aux:AnimalTypeClass.Aux[Food,AnimalInstance]) = {
aux.feed(food)
}
feedExplicitInstance(AnimalTypeClass.CatInstance)(new CatFood())
feedImplicitInstance(new CatFood())
}
trait Food{
def eat():Unit
}
trait AnimalTypeClass [AnimalInstance] {
type FoodThatAnimalLikes <: Food
def feed(f:FoodThatAnimalLikes)=f.eat()
}
trait Cat
class CatFood extends Food{
override def eat(): Unit = println("meow")
}
object AnimalTypeClass {
type Aux[Food,Animal]= AnimalTypeClass[Animal] {type FoodThatAnimalLikes = Food}
implicit object CatInstance extends AnimalTypeClass[Cat]{
override type FoodThatAnimalLikes = CatFood
}
}
So I had to change animal to aux in feedImplicitInstance and object Cat into object AnimalTypeClass and now everything works fine.
Of course the original question was a little bit trickier:
How can I write feedImplicitInstance[Cat](new CatFood()) ?
Jasper's answer is the answer for the original question. I have not understood his answer yet perfectly - I need to find some time to read carefully, hopefully very soon.

Advise on exercise with scala abstract types

I'm looking for an advise on this exercise I'm doing - specifically, what areas can be improved/shortened? I don't like that to feed the animals I have to use asInstanceOf but cannot find a better way. An idea is that I need to run this function for millions of objects so performance is important.
P.S. Would generics perform better in my case?
trait Animal {
type suitableFood
def eat(f: suitableFood)
}
trait Food
case class Grass (val color: String) extends Food
case class Fish (val size: String, val condition: String) extends Food
trait GrassT{
type suitableFood = Grass
}
trait FishT{
type suitableFood = Fish
}
class Cow extends Animal with GrassT{
def eat (f: suitableFood) = {
println("moo " + f.color)
}
}
class Sheep extends Animal with GrassT{
def eat (f: suitableFood) = {
println("baaa " + f.color)
}
}
class Cat extends Animal with FishT{
def eat (f: suitableFood) = {
println("mew " + f.size + " " + f.condition)
}
}
val cow = new Cow
val sheep = new Sheep
val whiteCat = new Cat
val blackCat = new Cat
val fish = new Fish("small", "fresh")
val grass = new Grass("green")
val cats = List(whiteCat, blackCat)
val farmAnimals = List(sheep, cow)
def feedAnimals[F <: Food](food: F, animals: List[Animal]) = {
animals.foreach(a => a.eat(food.asInstanceOf[a.suitableFood]))
}
feedAnimals(grass, farmAnimals)
feedAnimals(fish, cats)
You just need to put a stricter type restriction on feedAnimals:
def feedAnimals[F <: Food](food: F, animals: List[Animal { type suitableFood = F }]) =
animals.foreach(_.eat(food))
Or:
def feedAnimals[F <: Food, A <: Animal { type suitableFood = F }](food: F, animals: List[A]) =
animals.foreach(_.eat(food))
Unlike the solution using asInstanceOf, these solutions will fail at compile time (as opposed to runtime) for statements like feedAnimals(grass, cats), as desired.
Generics might be simpler in this case, if you prefer.

Abstract Over TypeClass

Starting with some simple code:
trait Moveable[A] {
def move(a: A): A
}
trait Animal {
def kick[A <: Animal: Moveable](a: A): A = implicitly[Moveable[A]] move a
}
object Cat {
implicit object CatMoveable extends Moveable[Cat] {
def move(cat: Cat): Cat = cat copy (pos = cat.pos + 4)
}
}
case class Cat(pos: Int) extends Animal
case class Dog(pos: Int) extends Animal
val someAnimal: Animal = Dog(0)
val kickedCat: Cat = someAnimal kick Cat(0)
println(kickedCat) // Cat(4)
I decided to distinguish between, let´s say, Quadruped and Biped animals:
trait FourFeetMoveable[A] {
def moveWithFourFeets(a: A): A
}
trait TwoFeetMoveable[A] {
def moveWithTwoFeets(a: A): A
}
trait Animal {
def kick[A <: Animal /*: ??? */](a: A): A
}
trait Quadruped extends Animal {
def kick[A <: Animal: FourFeetMoveable](a: A): A = implicitly[FourFeetMoveable[A]] moveWithFourFeets a
}
trait Biped extends Animal {
def kick[A <: Animal: TwoFeetMoveable](a: A): A = implicitly[TwoFeetMoveable[A]] moveWithTwoFeets a
}
object Chicken {
implicit object ChickenTwoFeetMoveable extends TwoFeetMoveable[Chicken] {
def moveWithTwoFeets(chicken: Chicken): Chicken = chicken copy (pos = chicken.pos + 2)
}
}
case class Dog(pos: Int) extends Quadruped
case class Chicken(pos: Int) extends Biped
val someAnimal: Animal = Dog(0)
val kickedChicken: Chicken = someAnimal kick Chicken(0)
println(kickedChicken) // Chicken(2)
A must have is to have two totally different typeclasses FourFeetMoveable and TwoFeetMoveable, so I can´t abstract over them with something like this:
trait Moveable[A] {
def move(a: A): A
}
So how can I abstract over the typeclasses used as a context bound at method kick in trait Animal(see the ???)?
EDIT
Sorry, I should have made my example clearer. Lets say the effect of being kicked can be some movement or some other action. I wanted to abstract over that effect with a typeclass.
In the following code I am showing what I mean and also used an abstract type member KickingEffect to abstract over the required typeclass, as 0__ proposed:
trait StumbleEffect[A <: Animal] {
def stumble(a: A): A
}
trait GlideEffect[A <: Animal] {
def glide(a: A): A
}
trait Animal {
type KickingEffect[A <: Animal]
def kick[A <: Animal: KickingEffect](a: A): A
}
trait Biped extends Animal {
type KickingEffect[A <: Animal] = StumbleEffect[A]
override def kick[A <: Animal: StumbleEffect](a: A): A = implicitly[StumbleEffect[A]] stumble a
}
trait Quadruped extends Animal {
type KickingEffect[A <: Animal] = GlideEffect[A]
override def kick[A <: Animal: GlideEffect](a: A): A = implicitly[GlideEffect[A]] glide a
}
object Dog {
implicit object DogGlideEffect extends GlideEffect[Dog] {
def glide(dog: Dog): Dog = dog copy (pos = dog.pos + 4)
}
}
case class Dog(pos: Int) extends Quadruped
case class Cat(pos: Int) extends Quadruped
case class Chicken(pos: Int) extends Biped
But then I ran into another problem, when it comes to sequences of animals:
type Beast[A <: Animal, KE[_ <: Animal]] = A { type KickingEffect[X <: Animal] = KE[X] }
val dogBeast: Beast[Dog, GlideEffect] = Dog(0) // fine
type GlideBeasts[A <: Quadruped] = Beast[A, GlideEffect]
val glideBeasts: Seq[GlideBeasts[Quadruped]] = Seq(Dog(0), Cat(0)) // fine
def kickAll[A <: Animal, KE[_ <: Animal], KA <: Animal](kicker: Beast[A, KE])(animals: Seq[KA])(implicit ev: kicker.KickingEffect[KA]): Seq[KA] = {
for (a <- animals) yield kicker kick a
}
val cat = Cat(0)
val dog = Dog(0)
kickAll(cat)(Seq(dog)) // wrong inferred kinds of type arguments
kickAll[Cat, GlideEffect, Dog](cat)(Seq(dog)) // missing implicit evidence
Like this?
trait Moveable[A] {
def move(a: A): A
}
trait FourFeetMoveable[A] extends Moveable[A]
trait TwoFeetMoveable[A] extends Moveable[A]
trait Animal {
type CanKick[A] <: Moveable[A]
def kick[A <: Animal : CanKick](a: A): A = implicitly[CanKick[A]] move a
}
trait Quadruped extends Animal {
type CanKick[A] = FourFeetMoveable[A]
}
trait Biped extends Animal {
type CanKick[A] = TwoFeetMoveable[A]
}
Regarding your edit: I would recommend at this point not to further try to model that with types, unless it is really an extremely crucial point in your application or a pure thought experiment. You can get easily overambitious with type-safe design, then the ratio between design effort and application value is getting to big; I would just drop some compile-time safety and go for pattern matching and runtime errors.
If you do want to follow the types route, as soon as you have collections, you will need something like HLists to preserve the individual types of the collection members.
Anyway, you can make your example work (with explicit type parameters):
def kickAll[A <: Animal, KE[_ <: Animal], KA <: Animal](
kicker: Beast[A, KE])(animals: Seq[KA])(implicit effect: KE[KA]): Seq[KA] = {
for (a <- animals) yield kicker kick a
}
val cat = Cat(0)
val dog = Dog(0)
kickAll(cat)(Seq(dog)) // still doesn't figure out the types
kickAll[Cat, GlideEffect, Dog](cat)(Seq(dog)) // ok!
As said, the tricky or impossible part comes when you try to do this with heterogeneous lists (e.g. requiring different effects). You might get away with using a helper type class for the elements of the sequence, instead, so that implicits can be resolved per item before.
As a side note, I found it always useful to not introduce type bounds till the moment they are really needed. Not only do you safe a lot of typing (no pun intended), but you keep options open (e.g. for later variance annotations). The following is totally sufficient:
trait StumbleEffect[A] {
def stumble(a: A): A
}
trait GlideEffect[A] {
def glide(a: A): A
}
trait Animal {
type KickingEffect[A]
def kick[A : KickingEffect](a: A): A
}
trait Biped extends Animal {
type KickingEffect[A] = StumbleEffect[A]
override def kick[A : StumbleEffect](a: A): A =
implicitly[StumbleEffect[A]] stumble a
}
trait Quadruped extends Animal {
type KickingEffect[A] = GlideEffect[A]
override def kick[A : GlideEffect](a: A): A = implicitly[GlideEffect[A]] glide a
}
etc.