Scala - Zřetězené porovnávání

Perl 6 / Raku má neuvěřitelně bohatou syntaxi a jednou z tisíce
vychytávek je zřetězené porovnávání, kdy se může zapsat podmínku ve
tvaru 20 <= $x <= 25
. Když se na to podíváte, dává to smysl na první pohled,
ale většina jazyků výraz nevyhodnotí jako 20 <= $x && $x <= 25
, ale jako (20
<= $x) <= 25
, což není, co by člověk chtěl.
Nicméně zřetězené porovnávání se dá docela pěkně implementovat ve Scale.
case class CC[T <% Ordered[T]](val v: T, val bool: Boolean = true) { // view bound T <% Ordered[T] zaručuje, že T má metody <, >, <=, >= private def make(x: Boolean) = new CC(v, bool && x) def <<:(a: CC[T]) = make(a.v < v) // <: je klíčové slovo - tzv. upper type bound def :<<(a: CC[T]) = make(v < a.v) def >>:(a: CC[T]) = make(a.v > v) // >: je klíčové slovo - tzv. lower type bound def :>>(a: CC[T]) = make(v > a.v) def <=:(a: CC[T]) = make(a.v <= v) def :<=(a: CC[T]) = make(v <= a.v) def >=:(a: CC[T]) = make(a.v >= v) def :>=(a: CC[T]) = make(v >= a.v) } implicit def v2cc[T <% Ordered[T]](v: T) = new CC(v) implicit def cc2b[T](v: CC[T]) = v.bool // test import scala.language.implicitConversions val a = 20 val b = 15 println(if (10 <<: a :<< 30) "ok" else "chyba") println(if (10 <<: a :<< 15) "chyba" else "ok") println(if (10 <<: a :>> b) "ok" else "chyba")
Metody končící dvojtečkou se v pozici operátorů volají v opačném pořadí. Metody se bohužel nemůžou jmenovat <:
a >:
, protože to jsou klíčová slova. Teoreticky všechny metody začínající dvojtečkou by mohly mít podobu standardních operátorů porovnání (tedy místo :<<
by se použilo <
, místo :>>
pak >
atd), ale z důvodu konzistence a jako signál, že se děje něco nekalého, jsem nechal tvar takový jaký je.
Mimochodem, příklad funguje díky tomu, že kompilátor implicitními konverzemi transformuje výraz 10 <<: 15 :<< 20
na cc2b(bv2cc(15).<<:(10).:<<(20))
.
Verze pro Scalu 3 je téměř identická (a kromě view bounds bude fungovat i ta
předchozí). Změnilo se jen přejmenování implicit
na given
, které nemusí mít
jméno a použití typu Conversion
, který je jediný způsob, jak dělat implicitní
konverze. Ty vyšly z módy jako matoucí a potenciálně velice nebezpečná
vlastnost jazyka a ve třetí verzi je jejich definice omezení na typ
Conversion
a použití musí předcházet import
scala.language.implicitConversions
.
case class CC[T: Ordering](v: T, boolVal: Boolean) { import Ordering.Implicits.infixOrderingOps def <<:(a: CC[T]) = CC(v, boolVal && a.v < v) def :<<(a: CC[T]) = CC(v, boolVal && v < a.v) def >>:(a: CC[T]) = CC(v, boolVal && a.v > v) def :>>(a: CC[T]) = CC(v, boolVal && v > a.v) def <=:(a: CC[T]) = CC(v, boolVal && a.v <= v) def :<=(a: CC[T]) = CC(v, boolVal && v <= a.v) def >=:(a: CC[T]) = CC(v, boolVal && a.v >= v) def :>=(a: CC[T]) = CC(v, boolVal && v >= a.v) } given [T: Ordering]: Conversion[T, CC[T]] = new CC(_, true) given [T]: Conversion[CC[T], Boolean] = _.boolVal import scala.language.implicitConversions // ...