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

Initialize a type variable with dynamic / concrete type

我正在学习 Scala,我正在尝试创建一个类型 class 来解决 "Every animal eats food, but the type of food depends on the animal" 问题。我有一个 Eats 类型 class 和上下文边界:

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

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

AnimalEdible 都是抽象的 classes。 (简化的)Animal 界面看起来像这样

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

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

implicit val cowEatsGrass = Eats[Cow, Grass]

类似于您在 Haskell 中编写 instance Eats Cow Grass 的方式。但是,现在我需要为 Animal class 的所有子类型指定抽象类型 This 才能使 Animal 接口中的签名生效:

class Cow extends Animal { type This = Cow }

这是多余的。

因此我的问题:我能否以某种方式初始化 Animal 中的类型变量 This 以便它始终反映具体类型,类似于我的方式可以使用 getClass?

请求动态类型

如果将第一个操作数 a: A 传递给有机会推断外部可见类型的方法/class 构造函数,则不会出现此问题 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

这里,EatsOps[A <: Animal]可以先推断出A是什么,然后在eat[B <: Animal]中可以推断出B是什么,利用两者的信息AB 插入正确的隐式。没有类型成员,扩展 Animal.

时无需执行任何操作

有点像 X-solution 到 XY-problem。而且,是的,我重用了 Animal 而不是 Food...


更新

如果你想在调用 eat 时访问特定 Animal 实现的一些私有方法,通常的方法是将所有基本功能移动到 Eats 特征,然后在特定 Animal 的伴随对象中提供 Eats 的实例。例如,下面是我们如何让 Cat 在实际吃掉 Bird 之前做一些不可思议的 private 事情:

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

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

抽象类型知道具体类型的标准方法是将具体类型传递给抽象类型(这称为"F-bounded polymorphism"):

abstract class Animal[This <: Animal[_]] {
  def eat[A <: Edible](food: A)(implicit e: Eats[This, A]) = ???
}

class Cow extends Animal[Cow]

Animal class 现在知道定义了 eat 方法的具体类型。

请注意,您需要调整对 Animal 的引用以添加类型参数:

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

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

通常在 type-level 编程类型 This 中手动定义子类型。例如

https://github.com/slick/slick/blob/master/slick/src/main/scala/slick/ast/Node.scala#L129

https://github.com/slick/slick/blob/master/slick/src/main/scala/slick/ast/Node.scala#L151

等等

也可以使用宏注解自动生成类型This

import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

class This extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro thisMacro.impl
}

object thisMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._
    annottees match {
      case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
        val tparams1 = tparams.map {
          case q"$_ type $name[..$_] >: $_ <: $_" => tq"$name"
        }
        q"""
            $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
              type This = $tpname[..$tparams1]
              ..$stats
            }

            ..$tail
          """

      case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        q"""
            $mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
              type This = $tname.type
              ..$body
            }
          """

      case _ => c.abort(c.enclosingPosition, "not class or object")
    }

  }
}

    @This
    class Cow extends Animal 

//Warning:scalac: {
//  class Cow extends Animal {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    type This = Cow
//  };
//  ()
//}

不幸的是,由于注解只能改变它的被注解者,我们不能只注解抽象 class 以便为所有子class 生成类型 This

像这样考虑类型 class 实现

  sealed trait Food
  case object Grass extends Food
  case object Meat extends Food

  sealed trait Animal
  case object Cow extends Animal
  case object Lion extends Animal

  @scala.annotation.implicitNotFound("${A} does not eat ${F}. Yuk!")
  trait CanEat[A <: Animal, F <: Food] {
    def eat(animal: A, food: F)
  }

  implicit val cowCanEatGrass = new CanEat[Cow.type, Grass.type] {
    def eat(animal: Cow.type, food: Grass.type) = println("yum yum yum...delicious")
  }

  def eat[A <: Animal, F <: Food](animal: A, food: F)(implicit canEat: CanEat[A, F]) = 
    canEat.eat(animal, food)

输出

  eat(Cow, Grass) // yum yum yum...delicious
  eat(Cow, Meat)  // error: Cow.type does not eat Meat.type. Yuk!