2013-06-27 2 views
3

Допустим, у меня есть следующий:Типа границы и шаблон соответствия в Scala

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 реализацию, матч по определенному типу, а затем возвращать наиболее специфичными type возможно. (Я знаю, что это не может быть умной вещью, но потерпите.) Скажем, что-то вроде:

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

Если у меня есть логика teacherGreeting внутри greet, у меня нет никаких проблем. Итак, почему компилятор не знает, что P в этом разделе кода должно быть 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

При компиляции greet(p), компилятор должен определить тип P в

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

Если затем определяется подкласс Person и вызвать приветствовать на этом экземпляре:

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

Предполагаемая тип P определяется во время компиляции, он не зависит от типа среды выполнения родителя. Поэтому компилятор должен был бы вывести P как Parent: greet[Parent](parent).

Но тогда как будут набираться эти выражения?Для того, чтобы напечатать чек, так как P является Parent, они должны быть типа (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 во второй случай, это, вероятно, потому, что вы возвращаете (person, "str"). И в этом случае person будет типа P наверняка. Но когда вы передаете его через teacherGreeting, компилятор не знает, что вы возвращаете переданный аргумент типа P. Насколько нам известно, вы можете вернуться Teacher("another", List()).

Редактировать: так что думать о том, как сохранить тип P, это (громоздкий) способ обойти это. Вы хотите сохранить тип с помощью вызова teacherGreeting. Это можно сделать так. Используйте параметр Q типа, который будет определен как P от 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 является 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) 
} 
+0

Спасибо, это очень полезное объяснение! – pr1001

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

Все работает просто отлично.

2

На самом деле это может быть еще короче, потому что я не вижу никаких причин, в unapplying учителя в PatMat:

def greet(person: Person): (Person, String) = person match { 
    case Student(name) => (person, s"Hello $name") 
    case teacher: Teacher => teacherGreeting(teacher) 
} 
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) 

записку в сторону : Вам действительно не нужна привязка типа на Person, это скорее для документации/предотвращения злоупотреблений.

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 
}