Под «идиоматическим» я предполагаю, что вы говорите о «Идиомах» Макбрайда и Патерсона в их статье Прикладное программирование с эффектами . : О)
Вот как бы вы использовали их идиомы для проверки упорядоченности коллекции:
import scalaz._
import Scalaz._
case class Lte[A](v: A, b: Boolean)
implicit def lteSemigroup[A:Order] = new Semigroup[Lte[A]] {
def append(a1: Lte[A], a2: => Lte[A]) = {
lazy val b = a1.v lte a2.v
Lte(if (!a1.b || b) a1.v else a2.v, a1.b && b && a2.b)
}
}
def isOrdered[T[_]:Traverse, A:Order](ta: T[A]) =
ta.foldMapDefault(x => some(Lte(x, true))).fold(_.b, true)
Вот как это работает:
Любая структура данных T[A]
, где существует реализация Traverse[T]
, может быть пройдена с помощью Applicative
функтора, или «идиома», или «сильный слабый моноидальный функтор». Так уж получилось, что каждый Monoid
бесплатно вызывает такую идиому (см. Раздел 4 статьи).
Моноид - это просто ассоциативная бинарная операция над каким-либо типом и элемент идентификации для этой операции. Я определяю Semigroup[Lte[A]]
(полугруппа такая же, как моноид, за исключением без элемента идентичности), ассоциативная операция которого отслеживает меньшее из двух значений и является ли левое значение меньше правого значения. И, конечно, Option[Lte[A]]
- это просто моноид, свободно генерируемый нашей полугруппой.
Наконец, foldMapDefault
пересекает тип сбора T
в идиоме, вызванной моноидом. Результат b
будет содержать значение true, если каждое значение было меньше, чем все последующие (что означает, что коллекция была упорядочена), или None
, если T
не имеет элементов. Поскольку пустой T
отсортирован по соглашению, мы передаем true
в качестве второго аргумента в конечный fold
Option
.
В качестве бонуса, это работает для всех перемещаемых коллекций. Демо:
scala> val b = isOrdered(List(1,3,5,7,123))
b: Boolean = true
scala> val b = isOrdered(Seq(5,7,2,3,6))
b: Boolean = false
scala> val b = isOrdered(Map((2 -> 22, 33 -> 3)))
b: Boolean = true
scala> val b = isOrdered(some("hello"))
b: Boolean = true
Тест:
import org.scalacheck._
scala> val p = forAll((xs: List[Int]) => (xs /== xs.sorted) ==> !isOrdered(xs))
p:org.scalacheck.Prop = Prop
scala> val q = forAll((xs: List[Int]) => isOrdered(xs.sorted))
q: org.scalacheck.Prop = Prop
scala> p && q check
+ OK, passed 100 tests.
И это как вы делаете идиоматический обход, чтобы определить, заказана ли коллекция.