用动态/具体类型初始化类型变量

Nik*_*est 3 types scala implicit typeclass type-members

我正在学习Scala,并且尝试创建类型类来解决“每种动物都吃食物,但是食物的类型取决于动物”的问题。我有一个Eats带有上下文范围的类型类:

trait Eats[A <: Animal, B <: Edible]

object Eats {
    def apply[A, B]: Eats[A, B] = new Eats[A, B] {}
}
Run Code Online (Sandbox Code Playgroud)

AnimalEdible为抽象类。(简化的)Animal界面看起来像这样

abstract class Animal {
    type This // concrete type
    def eat[A <: Edible](food: A)(implicit e: Eats[This, B]) = // ...
}
Run Code Online (Sandbox Code Playgroud)

我的目标是animal.eat(food)仅在给定类型的动物和食物存在实例(范围内的隐含值)的情况下才允许调用。为此,我创建了一个EatingBehaviour对象,该对象基本上包含所有关系的实例。例如 宣布奶牛吃草,我添加一行

implicit val cowEatsGrass = Eats[Cow, Grass]
Run Code Online (Sandbox Code Playgroud)

类似于您instance Eats Cow Grass在Haskell中的编写方式。但是,现在我需要为ThisAnimal类的所有子类型指定抽象类型,以便Animal接口中的签名起作用:

class Cow extends Animal { type This = Cow }
Run Code Online (Sandbox Code Playgroud)

这是多余的。

因此,我的问题是:我可以以某种方式初始化类型变量ThisAnimal以使其始终反映具体的类型,类似于我可以使用来请求动态类型getClass吗?

And*_*kin 5

如果将第一个操作数传递a: A给可以推断外部可见类型的方法/类构造函数,则不会发生此问题A

trait Animal
trait Eats[A <: Animal, B <: Animal]

object Eats {
    def apply[A <: Animal, B <: Animal]: Eats[A, B] = new Eats[A, B] {}
}

implicit class EatsOps[A <: Animal](a: A) {
    def eat[B <: Animal](food: B)(implicit e: Eats[A, B]) = 
      printf(s"%s eats %s\n", a, food)
}

case class Cat() extends Animal
case class Bird() extends Animal
case class Worm() extends Animal

implicit val e1 = Eats[Cat, Bird]
implicit val e2 = Eats[Bird, Worm]

val cat = Cat()
val bird = Bird()
val worm = Worm()

// c eat c // nope
cat eat bird
// c eat w // nope

// b eat c // nope
// b eat b // nope
bird eat worm 

// w eat c // nope
// w eat b // nope
// w eat w // nope
Run Code Online (Sandbox Code Playgroud)

在这里,EatsOps[A <: Animal]可以先推断出什么A,然后在eat[B <: Animal]其中推断出什么B,并使用有关两者的信息AB插入正确的隐式。没有类型成员,并且在extend时无需执行任何操作Animal

这是XY问题的X解决方案。是的,我重用了Animal而不是Food...


更新资料

如果要Animal在调用时访问特定实现的某些私有方法eat,则通常的方法是将所有基本功能移到Eatstrait中,然后Eats在特定的伴随对象中提供的实例Animal。例如,这是我们如何让a 在实际吃掉a之前Cat先做其不可思议private的工作Bird

trait Eats[A <: Animal, B <: Animal] {
  def apply(a: A, food: B): Unit
}

object Eats {
    def apply[A <: Animal, B <: Animal]: Eats[A, B] = new Eats[A, B] {
      def apply(animal: A, food: B) = println(s"${animal} eats ${food}")
    }
}

implicit class EatsOps[A <: Animal](animal: A) {
    def eat[B <: Animal](food: B)(implicit e: Eats[A, B]) = e(animal, food)
}

case class Cat() extends Animal {
  private def privateCatMethod(b: Bird): Unit = {}
}

object Cat {
  implicit val catsEatBirds: Eats[Cat, Bird] = new Eats[Cat, Bird] {
    def apply(c: Cat, b: Bird): Unit = {
      c.privateCatMethod(b)
      println(s"{c} eats {b}")
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

其余代码将保持不变,只是不再需要e1: Eats[Cat, Bird]任何代码。