jhegedus
jhegedus

Reputation: 20653

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
  }
}

Upvotes: 1

Views: 216

Answers (3)

jhegedus
jhegedus

Reputation: 20653

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.

Upvotes: 0

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149628

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.

Upvotes: 1

Jasper-M
Jasper-M

Reputation: 15086

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
  }
}

Upvotes: 1

Related Questions