3

假设我有以下内容:

trait Person {
  val name: String
}
case class Student(val name: String) extends Person
case class Teacher(val name: String, students: List[Student]) extends Person

我想要一个函数函数,它可以采用任何Person实现,匹配特定类型,然后返回可能的最特定类型。(我知道这可能不是最聪明的事情,但请耐心等待。)让我们这样说:

def teacherGreeting(teacher: Teacher): (Teacher, String) = {
  val names = teacher.students.map(_.name).mkString(", ")
  (teacher, s"Hello ${teacher.name}, your students are $names")
}

def greet[P <: Person](person: P): (P, String) = person match {
  case Student(name) => (person, s"Hello $name")
  case Teacher(name, students) => teacherGreeting(person)
}

但后来我得到:

<console>:19: error: type mismatch;
 found   : P
 required: Teacher
             case Teacher(name, students) => teacherGreeting(person)
                                                         ^

如果我有teacherGreetinginside of的逻辑greet,我没有任何问题。那么,为什么编译器不知道P在这个代码分支中必须是 a Teacher

如果我使用匹配的值:

def greet[P <: Person](person: P): (P, String) = person match {
  case Student(name) => (person, s"Hello $name")
  case teacher @ Teacher(name, students) => teacherGreeting(teacher)
}

错误只是稍后发生,结果为teacherGreeting, 而不是输入:

error: type mismatch;
 found   : (Teacher, String)
 required: (P, String)
             case teacher @ Teacher(name, students) => teacherGreeting(teacher)
                                                                  ^

有没有办法避免铸造?

4

5 回答 5

4

编译时,编译器greet(p)必须推断类型P

def greet[P <: Person](person: P): (P, String)

如果您随后定义了 Person 的子类并在该实例上调用 greet :

class Parent(val name: String) extends Person
val parent = new Parent("Joe")
val (p, greeting) = greet(parent)

推断类型 P 是在编译时确定的,它不依赖于父级的运行时类型。所以编译器必须推断PParent: greet[Parent](parent)

但是,这些表达式将如何输入呢?为了进行类型检查,因为Pis Parent,它们必须是 type (Parent, String)

case teacher @ Teacher(name, students) => teacherGreeting(teacher)
case Teacher(name, students) => teacherGreeting(person)

在第一种情况下,返回类型teacherGreeting(teacher)(Teacher, String)。并且Teacher不是Parent. 返回类型不匹配。

在第二种情况下,您正在调用teacherGreeting(person: Parent),所以它是错误的参数类型。

当你说如果你teacherGreeting在第二种情况下内联正文没有问题时,那可能是因为你 return (person, "str")。在那种情况下,person肯定是类型P。但是当你传递它时teacherGreeting,编译器并不知道你返回的是传递的 type 参数P。据我们所知,你可能会回来Teacher("another", List())

编辑:因此考虑如何在P此处保留类型是一种(繁琐的)方法。您想通过teacherGreeting调用保留类型。这可以像这样完成。使用Q将被推断为Pfrom的类型参数greet

def teacherGreeting[Q <: Teacher](teacher: Q): (Q, String) = {
  val names = teacher.students.map(_.name).mkString(", ")
  (teacher, s"Hello ${teacher.name}, your students are $names")
}  

告诉编译器teacher是 aP a Teacher

def greet[P <: Person](person: P): (P, String) = person match {
  case Student(name) => (person, s"Hello $name")
  case teacher: (P with Teacher) => teacherGreeting(teacher)
} 
于 2013-06-28T08:20:52.467 回答
2

实际上它可以更短,因为我看不出有任何理由在 PatMat 中取消申请老师:

def greet(person: Person): (Person, String) = person match {
  case Student(name)    => (person, s"Hello $name")
  case teacher: Teacher => teacherGreeting(teacher)
}
于 2013-06-27T20:24:12.170 回答
1

您可以为此使用类型类:

trait Greeter[P <: Person] {
  def greet(person: P): (P, String)
}

object Greeter {
  implicit object studentGreeter extends Greeter[Student] {
    def greet(student: Student) = (student, s"Hello ${student.name}")
  }
  implicit object teacherGreeter extends Greeter[Teacher] {
    def greet(teacher: Teacher) = {
      val names = teacher.students.map(_.name).mkString(", ")
      (teacher, s"Hello ${teacher.name}, your students are $names")
    }
  }
}

def greet[P <: Person](person: P)(implicit gr: Greeter[P]) = gr.greet(person)

顺便说一句:您实际上并不需要类型绑定 on Person,而是用于记录/防止滥用。

于 2013-06-27T20:45:39.483 回答
1

像其他答案一样,我想激发错误信息。

奇怪的是,推断的类型对提取器模式很满意,而对构造器模式不满意(也就是说,如果Teacher是一个案例类,它会看到t.type而不是P)。

package teachers

trait Person {
  def name: String
  override def toString = name
}
case class Student(name: String) extends Person
//case class Teacher(name: String, students: List[Student]) extends Person
class Teacher(val name: String, val students: List[Student]) extends Person
object Teacher {
  def apply(name: String, students: List[Student]) = new Teacher(name, students)
  def unapply(teacher: Teacher) = Some((teacher.name, teacher.students))
}
class Substitute(name: String, students: List[Student]) extends Teacher(name, students)
object Substitute {
  def apply(name: String, teacher: Teacher) = new Substitute(name, teacher.students)
  def unapply(sub: Substitute) = Teacher.unapply(sub)
}

object Test extends App {
  def teacherGreeting[A <: Teacher](teacher: A, duration: String): (A, String) = {
    val names = teacher.students.map(_.name).mkString(", ")
    (teacher, s"Hello ${teacher.name}, your students for the $duration are $names")
  }

  def greet[P <: Person](person: P): (P, String) = person match {
    case Student(name)                  => (person, s"Sit down and be quiet, $name")
    case s @ Substitute(name, students) => teacherGreeting(s, "day")
    case t @ Teacher(name, students)    => teacherGreeting(t, "year")
  }
  import reflect.runtime.universe._
  def show[P <: Person : TypeTag](person: P) = implicitly[TypeTag[P]].tpe.typeSymbol.name

  val mary = Teacher("Mary", List("Dick","Jane").map(Student))
  val (who, msg) = greet(Substitute("Bob", mary))
  Console println s"$who is a ${show(who)}"
  Console println msg
}
于 2013-06-28T11:15:05.947 回答
0

我认为您实际上并不需要在greet. 当您更改greet为:

def greet(person: Person): (Person, String) = person match {
  case Student(name) => (person, s"Hello $name")
  case teacher @ Teacher(name, students) => teacherGreeting(teacher)
}

一切正常。

于 2013-06-27T20:05:58.727 回答