使用动态/具体类型初始化类型变量 [英] Initialize a type variable with dynamic / concrete type

查看:76
本文介绍了使用动态/具体类型初始化类型变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am learning Scala and I was trying to create a type class to solve the "Every animal eats food, but the type of food depends on the animal" problem. I have an Eats type class with context bounds:

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

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

,其中 Animal Edible 都是抽象类.(减少的) Animal 接口看起来像这样

with both Animal and Edible being abstract classes. The (reduced) Animal interface looks something like this

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

我的目标是仅在给定类型的动物和食物存在实例(作用域中的隐式值)的情况下,才允许以 animal.eat(food)的形式进行调用.为此,我创建了一个 EatBehaviour 对象,该对象基本上包含所有关系的实例.例如声明奶牛吃草,我要添加行

My goal is to allow calls in the form of animal.eat(food) only if there is an instance (an implicit value in scope) for the given type of animal and food. For this I created an EatingBehaviour object which basically contains instances for all relations. E. g. to declare that cows eat grass I add the line

implicit val cowEatsGrass = Eats[Cow, Grass]

类似于您在Haskell中编写实例的方式.但是,现在我需要为Animal类的所有子类型指定抽象类型 This ,以使 Animal 界面中的签名起作用:

similar to how you would write instance Eats Cow Grass in Haskell. However, Now i need to specify the abstract type This for all subtypes of the Animal class for the signature in the Animal interface to work:

class Cow extends Animal { type This = Cow }

这是多余的.

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

Hence my question: Can I somehow initialize the type variable This in Animal so that this always reflects the concrete type, similar to how I could ask for the dynamic type using getClass?

推荐答案

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

The problem doesn't occur if you pass the first operand a: A to a method / class constructor that has the opportunity to infer the externally visible type 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 时无需执行任何操作.

Here, EatsOps[A <: Animal] can first infer what A is, then in eat[B <: Animal] it can infer what B is, and using information about both A and B insert the correct implicit. There are no type members, and nothing has to be done when extending Animal.

这是XY问题的X解决方案.而且,是的,我重用了 Animal (动物)而不是 Food ...

It's a bit of an X-solution to an XY-problem. And, yeah, I reused Animal instead of Food...

更新

如果您要在调用 eat 时访问特定 Animal 实现的某些私有方法,则通常的做法是将所有基本功能移至 Eats 特征,然后在特定 Animal 的伴随对象中提供 Eats 的实例.例如,这是我们如何让 Cat 在真正吃掉 Bird 之前先做它奇怪的 private 东西:

If you want to access some private methods of a particular Animal implementation when invoking eat, the usual way to do this would be to move all the essential functionality into the Eats trait, and then provide instances of Eats in the companion object of a specific Animal. For example, here is how we could let a Cat do its uncanny private stuff before actually eating a 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] .

The rest of the code would remain unchanged, except that one doesn't need e1: Eats[Cat, Bird] any more.

这篇关于使用动态/具体类型初始化类型变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆