مرحبا يا هابر! أحمل إلى محكمتك ترجمة روسية لمقالتي عن الوسيط: الفرز في سكالا - مثال على متجر القطط . تم إعداد المقالة للقراء الذين يعرفون بنية لغة سكالا ولديهم المعرفة بالأدوات الأساسية للمكتبة القياسية.
على الرغم من حقيقة أن كل من Java و Scala يستخدمان JVM كمنصة وقت تشغيل ، فقد اكتسبت Scala شهرة كلغة أكثر تعبيرًا ، وذلك بفضل التكيف الكبير لمفاهيم البرمجة الوظيفية ومكتبة قياسية غنية. في هذه المقالة ، سأفكر في مثال على هذه التعبيرية ، محاولًا تصور كيف يمكن أن يتطور جزء صغير من قاعدة التعليمات البرمجية والمتطلبات المقابلة بمرور الوقت.
البيان الأولي للمشكلة
تخيل أن لدينا متجرًا للقطط تحت تصرفنا (لأن القطط هي الحيوان الأكثر شعبية في النظام البيئي Scala). نظرًا لخصائص الحصول على معلومات حول القطط المتاحة للبيع ، في بعض الحالات لا تأتي المعلومات من قاعدة البيانات ويجب فرزها يدويًا قبل إرسال استجابة HTTP إلى العميل أو معالجتها بأي طريقة أخرى. الهدف الرئيسي من مجال الموضوع ، بالطبع ، Cat
والذي في البداية لا يحتوي إلا على ثلاثة مجالات من الأنواع البدائية. الهدف هو تطوير واجهة برمجة التطبيقات لفرز مجموعات القطط التي تلبي المتطلبات التالية:
- يمكن تحديد ترتيب الفرز لكل من الحقول.
- لا يمكن تحديد ترتيب الفرز لأي من الحقول
- يجب أن يكون التصنيف ثابتًا للحقول بترتيب فرز غير محدد
- لكل حقل أولوية محددة مسبقًا عند الفرز (أي أن الفرز حسب الحقل
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
.
جميع نماذج الكود متاحة في مستودع جيثب هذا.