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

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

5 に答える 5

4

をコンパイルするとき、コンパイラは の型をgreet(p)推測する必要があります。P

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

次に、Person のサブクラスを定義し、そのインスタンスで greeting を呼び出した場合:

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

推論された型 P はコンパイル時に決定され、親の実行時の型には依存しません。したがって、コンパイラは : として推論する必要がありPます。Parentgreet[Parent](parent)

しかし、これらの式はどのように入力されるのでしょうか? チェックを入力するには、PisParentであるため、 type である必要があります(Parent, String)

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

最初のケースでは、戻り値の型はteacherGreeting(teacher)です(Teacher, String)。そして、TeacherではありませんParent。戻り値の型が一致しません。

2 番目のケースでは、呼び出しteacherGreeting(person: Parent)ているため、引数の型が間違っています。

2番目のケース内の本体をインライン化しても問題ないと言うときteacherGreeting、それはおそらくあなたが返すからです(person, "str"). その場合、確かpersonにタイプになります。Pしかし、それを渡すときteacherGreeting、コンパイラは渡された type の引数を返していることを知りませんP。あなたが戻ってくる可能性があることはわかっていますTeacher("another", List())

編集:ここで型を保持する方法を考えるPのは(面倒な)方法です。teacherGreeting呼び出しを通じて型を保持したい。これは、このように行うことができます。fromQとして推論される型パラメーターを使用します。Pgreet

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

teacheraP 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

他の回答と同様に、エラーメッセージを動機付けたいと思いました。

不思議なことに、推論された型はエクストラクタ パターンには適していますが、コンストラクタ パターンには適していません (つまり、 ifが の代わりにTeacher見えるケース クラス)。t.typeP

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 に答える
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)

余談: type-bound on は実際には必要ありません。Personむしろ、文書化/乱用の防止のためです。

于 2013-06-27T20:45:39.483 に答える
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 に答える