简体   繁体   English

在Scala中键入Bounds和Pattern Matching

[英]Type Bounds and Pattern Matching in Scala

Let's say I have the following: 假设我有以下内容:

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

I'd like a function function that could take any Person implementation, match on the specific type, and then return the most specific type possible. 我想要一个函数函数,可以接受任何Person实现,匹配特定类型,然后返回最具体的类型。 (I know this might not be the smartest thing, but bear with me.) Let's say something like: (我知道这可能不是最聪明的事情,但请耐心等待。)让我们说:

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

But then I get: 但后来我得到:

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

If I have the logic of teacherGreeting inside of greet , I don't have any problems. 如果我有teacherGreeting内心greet ,我就没有任何问题。 So, why doesn't the compiler know that P in this branch of the code must be a Teacher ? 那么,为什么编译器不知道代码的这个分支中的P 必须Teacher

If I use the matched value: 如果我使用匹配的值:

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

The error just happens later, with the result of teacherGreeting , instead of the input: 错误只是稍后发生,结果是teacherGreeting ,而不是输入:

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

Is there no way to avoid casting? 有没有办法避免施法?

When you compile greet(p) , the compiler has to infer the type P in 编译greet(p) ,编译器必须推断出类型P in

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

If you then defined a subclass of Person and call greet on that an instance: 如果你然后定义了Person的子类并在该实例上调用greet:

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

The inferred type P is determined at compile time, it does not depend on the runtime type of parent. 推断类型P在编译时确定,它不依赖于父类的运行时类型。 So the compiler would have to infer P as Parent : greet[Parent](parent) . 所以编译器必须将P推断为Parentgreet[Parent](parent)

But then how would these expressions be typed? 但那么这些表达式将如何输入呢? In order to type check, since P is Parent , they have to be of type (Parent, String) : 为了键入check,因为PParent ,它们必须是类型(Parent, String)

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

In the first case, the return type of teacherGreeting(teacher) is (Teacher, String) . 在第一种情况下, teacherGreeting(teacher)的返回类型是(Teacher, String) And Teacher is not a Parent . Teacher不是Parent The return type does not match. 返回类型不匹配。

In the second case, you are calling teacherGreeting(person: Parent) so it is wrong type for the argument. 在第二种情况下,您正在调用teacherGreeting(person: Parent)因此它是参数的错误类型。

When you say you don't have a problem if you inline the body of teacherGreeting inside the second case, that is probably because you return (person, "str") . 当你说你没有问题如果你在第二种情况下内联teacherGreeting的内容,那可能是因为你回来了(person, "str") And in that case person would be of type P for sure. 在那种情况下, person肯定会是P型。 But when you pass it through teacherGreeting , the compiler does not know that you are returning the passed argument of type P . 但是当你通过teacherGreeting传递它时,编译器不知道你正在返回P类型的传递参数。 For all we know you could be returning Teacher("another", List()) . 据我们所知,你可以回归Teacher("another", List())

Edit: so thinking on how to preserve the type P here is a (cumbersome) way to go about it. 编辑:所以考虑如何在这里保留类型P是一种(繁琐的)方式。 You want to retain the type through the teacherGreeting call. 您希望通过teacherGreeting呼叫保留该类型。 This can be done like this. 这可以这样做。 Use a type parameter Q that will be inferred as the P from greet : 使用类型参数Q ,将从greet推断为P

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

Tell the compiler that teacher is a P and a Teacher : 告诉编译器teacherP 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)
} 

Actually it can be even shorter, cause i don't see any reason in unapplying teacher in PatMat: 实际上它可能更短,因为我在PatMat中没有看到任何未应用教师的原因:

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

You can use type classes for this: 您可以使用类型类:

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)

A note aside: You don't really need the type-bound on Person , it's rather for documentation / prevention of abuse. 旁边的一个注释:你真的不需要Person上的类型绑定,而是需要文档/防止滥用。

Like the other answer, I wanted to motivate the error message. 像其他答案一样,我想激发错误信息。

Curiously, the inferred type is happy for the extractor pattern and unhappy with the constructor pattern (that is, if Teacher is a case class, where it sees t.type instead of P ). 奇怪的是,推断类型对于提取器模式很满意并且对构造函数模式不满意(也就是说,如果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
}

I'm thinking you don't really need a generic type on greet . 我在想你不需要一个普通类型的greet When you change your greet to: 当你改变你的greet

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

Everything works just fine. 一切正常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM