哈Ha!我将我在《媒介:在Scala中的排序-猫店的例子》一文的俄文翻译带上法庭。本文面向那些了解Scala语言语法并熟悉标准库基本工具的读者。
尽管Java和Scala都将JVM用作运行时平台,但由于对功能编程概念和丰富的标准库进行了重大修改,Scala作为一种更具表现力的语言而广为人知。在本文中,我将考虑这种表现力的示例,尝试想象一小部分代码库和相应需求会随着时间的推移而发展。
问题的初步陈述
想象一下,有一家猫店可供我们使用(因为猫是Scala生态系统中最受欢迎的动物)。由于获取有关可出售猫的信息的特殊性,在某些情况下,该信息不是来自数据库,因此必须在将HTTP响应发送给客户端或以其他任何方式处理之前手动进行分类。当然,主题区域的主要对象是Cat
一开始只有三个原始类型的字段。目标是开发一种用于对cat集合进行排序的API,该API可以满足以下要求:
- 可以为每个字段确定排序顺序。
- 可能没有为任何字段定义排序顺序
- 对于排序顺序不确定的字段,排序必须稳定
- 排序时每个字段都有一个预定义的优先级(即按字段排序
age
总是比按字段排序优先级更高name
)
第一次迭代
case class Cat(age: Int,
name: String,
available: Boolean)
由于问题的陈述相对简单,并且没有有关项目其他部分的信息,因此最好使初始解决方案简单。幸运的是,它scala.Ordering
已经定义了一种方便的方法Tuple3
,您可以使用该方法Ordering
为维3的元组创建实例,可以轻松地将此类的任何对象简化为Cat
。
, Tuple3
, . , — Tuple1
, Tuple2
, Tuple3
, Ordering
, . 9 (3 3 ), .
, API , " " . , 3 : (), ( ) "" ( ). (ADT):
sealed trait SortOrder
object SortOrder {
case object Keep extends SortOrder
case object Asc extends SortOrder
case object Desc extends SortOrder
}
Ordering
. SortOrder
, , , :
import common.OrderingUtil
import iteration1.SortOrder.{Asc, Desc, Keep}
object syntax {
implicit class OrderSyntax(val order: SortOrder) extends AnyVal {
def apply[A](ordering: Ordering[A]): Ordering[A] =
order match {
case Keep => OrderingUtil.identity
case Asc => ordering
case Desc => ordering.reverse
}
}
}
OrderingUtil.identity
— , A
, . : Ordering.by(_ => 0)
.
, , Ordering[Cat]
. CatOrdering
:
import iteration1.syntax._
object CatOrdering {
def of(idOrder: SortOrder,
nameOrder: SortOrder,
availableOrder: SortOrder): Ordering[Cat] =
Ordering
.Tuple3(idOrder(Ordering.Int), nameOrder(Ordering.String), availableOrder(Ordering.Boolean))
.on[Cat](cat => (cat.age, cat.name, cat.available))
}
(Ordering[Cat]
) CatOrdering.of
:
CatOrdering.of(SortOrder.Asc, SortOrder.Keep, SortOrder.Desc)
, ScalaTest ScalaCheck property-based . , . .
Cat
.
case class Cat(age: Int,
name: String,
available: Boolean,
owner: Option[String])
, — ( null
!), , , , . , 4 :
- , ()
- ,
- ,
- , ( )
(1 4) scala.Ordering.Option
, 2 . SortOrder
, :
sealed trait SortOrder
object SortOrder {
case class Asc(emptyFirst: Boolean) extends SortOrder
case class Desc(emptyFirst: Boolean) extends SortOrder
case object Keep extends SortOrder
object Asc {
def emptyFirst: Asc = Asc(emptyFirst = true)
def emptyLast: Asc = Asc(emptyFirst = false)
}
object Desc {
def emptyFirst: Desc = Desc(emptyFirst = true)
def emptyLast: Desc = Desc(emptyFirst = false)
}
}
SortOrder
. optional
Ordering[Option[A]]
A
, , SortOrder
. , , Ordering[Option[A]]
apply
, Ordering[Option[A]]
, . , A
apply
Option
. <:<
, StackOverflow ( Dotty Not
).
import common.OrderingUtil
import iteration2.sort_order.SortOrder._
object syntax {
private object OptionOrdering {
def apply[A](rootOrdering: Ordering[A],
emptyFirst: Boolean): Ordering[Option[A]] =
if (emptyFirst)
OptionOrdering.emptyFirst(rootOrdering)
else
OptionOrdering.emptyLast(rootOrdering)
def emptyFirst[A](rootOrdering: Ordering[A]): Ordering[Option[A]] =
(x: Option[A], y: Option[A]) => (x, y) match {
case (None, None) => 0
case (None, _) => -1
case (_, None) => 1
case (Some(a), Some(b)) => rootOrdering.compare(a, b)
}
def emptyLast[A](rootOrdering: Ordering[A]): Ordering[Option[A]] =
(x: Option[A], y: Option[A]) => (x, y) match {
case (None, None) => 0
case (None, _) => 1
case (_, None) => -1
case (Some(a), Some(b)) => rootOrdering.compare(a, b)
}
}
implicit class OrderSyntax(val order: SortOrder) extends AnyVal {
def optional[A](ordering: Ordering[A]): Ordering[Option[A]] =
order match {
case Keep => OrderingUtil.identity
case Asc(emptyFirst) => OptionOrdering(ordering, emptyFirst)
case Desc(emptyFirst) => OptionOrdering(ordering.reverse, emptyFirst)
}
def apply[A](ordering: Ordering[A]): Ordering[A] =
order match {
case Keep => OrderingUtil.identity
case Asc(_) => ordering
case Desc(_) => ordering.reverse
}
}
}
import iteration2.sort_order.SortOrder
import iteration2.sort_order.syntax._
import scala.Ordering.{Boolean => BooleanO, Int => IntO, String => StringO}
object CatOrdering {
def toOrdering(idOrder: SortOrder,
nameOrder: SortOrder,
availableOrder: SortOrder,
ownerOrder: SortOrder): Ordering[Cat] = {
Ordering
.Tuple4(idOrder(IntO), nameOrder(StringO), availableOrder(BooleanO), ownerOrder.optional(StringO))
.on[Cat](cat => (cat.age, cat.name, cat.available, cat.owner))
}
}
Ordering[Cat]
. , .
CatOrdering.toOrdering(
SortOrder.Asc.emptyFirst,
SortOrder.Asc.emptyFirst,
SortOrder.Asc.emptyFirst,
SortOrder.Asc.emptyFirst
)
, Option
, . .
. , :
- .
- - ,
SortOrder.Keep
. Cat
9. Tuple10
.
- . , Cat
10 . , , . , , SortOrder
. .
, , . Cat
. ( №1) ( №2), ( №3). (SortOrder
):
import java.time.LocalDate
case class Cat(age: Int,
name: String,
available: Boolean,
owner: Option[String],
breed: String,
furColor: String,
eyeColor: String,
registrationId: String,
lastHealthCheck: Option[LocalDate],
urgentSell: Boolean)
import java.time.LocalDate
import iteration3.sort_order.SortOrder
import iteration3.sort_order.syntax._
import scala.Ordering._
sealed trait CatField {
def toOrdering(sortOrder: SortOrder): Ordering[Cat]
}
object CatField {
case object Age extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder(Ordering.Int).on(_.age)
}
case object Name extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder(Ordering.String).on(_.name)
}
case object Available extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder(Ordering.Boolean).on(_.available)
}
case object Owner extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder.optional(Ordering.String).on(_.owner)
}
case object Breed extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder(Ordering.String).on(_.breed)
}
case object FurColor extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder(Ordering.String).on(_.furColor)
}
case object EyeColor extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder(Ordering.String).on(_.eyeColor)
}
case object RegistrationId extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder(Ordering.String).on(_.registrationId)
}
case object LastHealthCheck extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder.optional(Ordering.by[LocalDate, Long](_.toEpochDay)).on(_.lastHealthCheck)
}
case object UrgentSell extends CatField {
override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
sortOrder(Ordering.Boolean).on(_.urgentSell)
}
}
import common.OrderingUtil
import iteration3.sort_order.SortOrder
object CatOrdering {
def byFields(fields: Seq[(CatField, SortOrder)]): Ordering[Cat] =
if (fields.isEmpty) OrderingUtil.identity[Cat]
else {
val (head, headOrder) = fields.head
val (res, _) = fields.tail.foldLeft[(Ordering[Cat], Set[CatField])]((head.toOrdering(headOrder), Set())) {
case (acc@(_, presentFields), (field, _)) if presentFields.contains(field) =>
acc
case ((ordering, presentFields), (field, order)) =>
(ordering.orElse(field.toOrdering(order)), presentFields + field)
}
res
}
}
orElse
, , . thenComparing
Comparator
Java. Ordering
, Comparator
. , orElse
Scala, orElseBy
.
, , byFields
, . , OrderingUtil.identity
, . " ", foldLeft
, .
SortOrder.Keep
, , , . , , . HTTP , .
. , , , . ( ) , , . , , , , , .
.
尽管最终的实现在其他编程语言中可能看起来相似,但是在我看来,Scala在类型安全性和代码可理解性与可读性之间提供了很好的折衷方案。我希望该出版物有助于更好地了解如何在Scala中对集合进行排序,或者提出一些可以在与无关的代码中应用的想法Ordering
。
所有示例代码在此github存储库中都可用。