[英]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
推断为Parent
: greet[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,因为
P
是Parent
,它们必须是类型(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
: 告诉编译器
teacher
是P
和 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.