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

时间:2019-07-13 10:14:10

标签: scala types typeclass implicit type-members

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

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

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

,其中AnimalEdible都是抽象类。 (缩小的)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类的所有子类型指定抽象类型This,以使Animal接口中的签名起作用:

class Cow extends Animal { type This = Cow }

这是多余的。

我的问题:我可以通过某种方式初始化This中的类型变量Animal,以便它始终反映具体的类型,类似于我可以请求动态变量的方式使用getClass输入?

4 个答案:

答案 0 :(得分:6)

如果将第一个操作数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

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

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


更新

如果您要在调用Animal时访问特定eat实现的一些私有方法,通常的方法是将所有基本功能都移到Eats特性中,然后在特定Eats的伴随对象中提供Animal的实例。例如,下面是我们如何让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}")
    }
  }
}

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

答案 1 :(得分:3)

通常在类型级别的编程中,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
//  };
//  ()
//}

不幸的是,由于注释只能更改其注释,所以我们不能仅注释抽象类,因此将为所有子类生成类型This

答案 2 :(得分:2)

抽象类型知道具体类型的标准方法是将具体类型传递给抽象类型(这称为“ F界多态”):

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

class Cow extends Animal[Cow]

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

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

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

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

答案 3 :(得分:2)

考虑这样的类型类实现

  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!