#scala #pattern-matching
#scala #сопоставление с образцом
Вопрос:
У меня есть следующий класс Scala:
case class Person(firstName: String, lastName: String, age: Int)
extends Ordered[Person] {
def compare(that: Person): Int = {
if (this.lastName < that.lastName) -1
else if (this.lastName > that.lastName) 1
else if (this.firstName < that.firstName) -1
else if (this.firstName > that.firstName) 1
else this.age compare that.age
}
}
чтобы разрешить сортировку по фамилии, имени и возрасту.
Как я могу написать это, используя сопоставление с образцом? Я придумал следующее, но есть ли способ получше?
case class Person(firstName: String, lastName: String, age: Int)
extends Ordered[Person] {
def compare(that: Person): Int = {
that match {
case Person(_, thatLastName, _) if this.lastName < thatFile => -1
case Person(_, thatLastName, _) if this.lastName > thatFile => 1
case Person(thatFirstName, _, _) if this.firstName < thatFirstName => -1
case Person(thatFirstName, _, _) if this.firstName > thatFirstName => 1
case Person(_, _, thatAge) => this.age compare thatAge
}
}
}
ОБНОВЛЕНИЕ: Изменено на использование Ordering[A]
в соответствии с ответом Ландея:
implicit val personOrdering = new Ordering[Person] {
def compare(first: Person, second:Person): Int = {
second match {
case Person(_, thatLastName, _) if first.lastName < thatLastName => -1
case Person(_, thatLastName, _) if first.lastName > thatLastName => 1
case Person(thatFirstName, _, _) if first.firstName < thatFirstName => -1
case Person(thatFirstName, _, _) if first.firstName > thatFirstName => 1
case Person(_, _, thatAge) => first.age compare thatAge
}
}
}
case class Person(firstName: String, lastName: String, age: Int)
но кажется неудобным, что я только сопоставляю second
. Как я могу сделать это более «элегантным»?
Комментарии:
1. Для меня лучший способ, очевидно, без сопоставления с шаблоном в этом случае…
2. @Жан-Филипп Пелле: Но что происходит, когда вам нужно отсортировать по большему количеству столбцов? Я думаю (надеялся), что версия сопоставления с шаблоном будет более читаемой, возможно, с небольшим использованием пробелов (см. Обновление).
Ответ №1:
Предпочтительный способ в Scala — предоставить неявный порядок вместо Ordered , что намного более гибко и не создает головной боли в отношении наследования.
Что касается сопоставления с шаблоном, я не вижу лучшего способа, потому что результатом методов сравнения являются Int
s, которые не гарантированно равны -1, 0, 1. Решение Haskell для возврата объектов «enum» (LT, EQ, GT) намного чище и сопоставимо с шаблоном, но, похоже, Scala следовала традиции C / Java здесь по соображениям совместимости.
Конечно, вы могли бы развернуть свою собственную «платформу» для сравнения:
abstract sealed class CompResult(val toInt:Int) {
def andThen(next: => CompResult): CompResult
}
case object LT extends CompResult(-1) {
def andThen(next: => CompResult) = LT
}
case object EQ extends CompResult(0) {
def andThen(next: => CompResult) = next
}
case object GT extends CompResult(1) {
def andThen(next: => CompResult) = GT
}
implicit def int2Comp(n:Int) =
if (n == 0) EQ else if (n < 0) LT else GT
(("sdkfhs" compareTo "fldgkjdfl"):CompResult) match {
case LT => println("less")
case EQ => println("same")
case GT => println("more")
}
В вашем случае вы могли бы написать:
case class Person(firstName: String, lastName: String, age: Int)
extends Ordered[Person] {
def compare(that: Person): Int = {
(this.lastName compareTo that.lastName).
andThen (this.firstName compareTo that.firstName).
andThen (this.age compare that.age).toInt
}
}
Комментарии:
1. Круто! DSL для сравнения. Но это кажется немного «тяжелым» для сравнения с
Person
объектами :-).2. Учитывая, что Scala не поддерживает
Int
перечисления, наличие объектов вместо целых чисел повлияло бы на производительность. Например, -1, 0 и 1 являются сопоставимыми с образцом, и они могут преобразовываться в switch, тогда какLT
,EQ
иGT
не будут.3. @Daniel: Как всегда, существует компромисс между удобочитаемостью и производительностью, но, по крайней мере, у вас есть выбор (например, в отличие от Java). Конечно, я бы не рекомендовал свое решение для любого кода, критичного к производительности. Кстати, для более серьезных целей мы могли бы «обмануть» и использовать Java enums для LT, EQ и GT (но IMO это было бы действительно «слишком тяжело»).
4. Но использование Java enums потеряет хороший
andThen
метод, который является самой крутой особенностью вашего решения. Кстати, в Scalaz действительно есть что-то подобное… Интересно, есть ли у нихandThen
также?