使用动态/具体类型初始化类型变量
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] {}
}
Animal
和 Edible
都是抽象的 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
是什么,利用两者的信息A
和 B
插入正确的隐式。没有类型成员,扩展 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!
我正在学习 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] {}
}
Animal
和 Edible
都是抽象的 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
是什么,利用两者的信息A
和 B
插入正确的隐式。没有类型成员,扩展 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!