Published 2013-03-14.
Last modified 2019-07-12.
Time to read: 7 minutes.
We continue our exploration of parametric types with the topics of sorting and ordered collections.
The sample code for this lecture can be found in
courseNotes/
.
The definitions for ThingOrdered
and ThingOrdering
and their instances are provided in
courseNotes/
.
Ad-Hoc Sorting
We discussed Function1
in the Functions are First Class lecture of the
Introduction to Scala course,
and we showed how higher-order functions can be written that accept Function1
s in the
Higher-Order Functions lecture of this course.
As a reminder, a Function1
receives one parameter and returns a value.
For example, a Function1[A, B]
accepts an instance of type A
and returns an instance of type B
;
the shorthand notation is (A) => B
.
It is also useful to work with higher-order functions that accept FunctionN
s which receive more than one parameter when working with collections.
A common use case is to make a sorted copy of a collection according to an ad-hoc criterion by passing in an instance of a Function2
which compares two elements of the collection at a time as it walks through the collection.
The type of this comparison function is Function2[A, A, B]
, which is written in shorthand as (A, A) => B
.
The shorthand indicates that a Tuple2[A, A]
is actually passed as the argument list into the Function2
, and an instance of B
is returned.
The comparison function must accept two parameters of the same type, and return true
if the first argument precedes the second argument or false
otherwise.
Here is an example of passing an anonymous Function2[Int, Int, Boolean]
into the sortWith
higher-order function.
scala> List(3, 7, 5, 2).sortWith((x, y) => x < y)
res1: List[Int] = List(2, 3, 5, 7)
The sortWith
higher order function iterates through the collection, comparing pairs of elements, and sorts the intermediate results internally.
sortWith
is provided by the SeqOps
trait, which is mixed into all Scala collection types.
It returns a new sorted collection consisting of the elements of the original collection, ordered according to the comparison function passed into it.
sortWith
provides a stable sort, which means that elements that are equal as determined by the comparison function appear in the same order in the sorted sequence as they appeared in the original collection.
When we discussed the shorthand for writing FunctionN
s, we did not mention what happens if multiple parameters are supplied by a higher-order function to a function value.
Multiple underscores can be used in the shorthand, and they are bound in order.
For example, we can use shorthand to perform the same sort, as written above, like this.
scala> List(3, 7, 5, 2).sortWith(_ < _)
res2: List[Int] = List(2, 3, 5, 7)
Exercise – Sorting a List Using a Higher-Order Function
Use the REPL to sort List(3, -7, 5, -2)
according to the absolute value of each of the elements.
Hint
You can find the absolute value of a number using math.abs
.
Solution
scala> List(3, -7, 5, -2).sortWith(math.abs(_)<math.abs(_)) res0: List[Int] = List(-2, 3, 5, -7)
To run this solution, type:
$ sbt "runMain solutions.AbsSort"
Ordered Supports One Natural Ordering for a Collection Type
As we just saw, you can sort a collection upon demand, however you will often want to define a natural ordering for your collection.
Scala provides the abstract Ordered
trait for classes that have a default natural ordering.
It is easy to apply this trait: merely mix in the Ordered
trait into any class that needs to be sorted,
and define the methods that the Ordered
trait declares.
Note that the name of the class to be sorted is a type parameter for the abstract trait Ordered
.
This is why the name of the class, ThingOrdered
,
appears twice in the class definition in the following code example.
Also note that Ordered
requires the compare
method be implemented.
class ThingOrdered(val i: Int, val s: String) extends Ordered[ThingOrdered] { def compare(that: ThingOrdered) = { val primaryKey = this.i - that.i if (primaryKey!=0) primaryKey else this.s compare that.s } override def equals(other: Any) = { val that = other.asInstanceOf[ThingOrdered] this.i == that.i && this.s==that.s } override def hashCode = super.hashCode override def toString = s"ThingOrdered($i, $s)" }
The Ordered
trait does not provide a default implementation of equality, so if you define compare
I strongly suggest that you also redefine equals
, which is actually invoked by the ==
method.
Whenever you define equals
you should also define hashCode
as shown above.
Classes that implement the Ordered
trait can be sorted many ways, including.
- The
sorted
method, available to all collection types because they all are subclasses ofSeqOps
.sorted
uses the natural ordering of the collection. - The methods of the
scala.util.Sorting
singleton object (quickSort
andstableSort
). - The standard comparison operators
>
,<,
>=
and<=
.
Let’s define some ThingOrdered
instances and compare them:
scala> val thingOrdered1 = new ThingOrdered(1, "x") thingOrdered1: ThingOrdered = ThingOrdered(1, x)
scala> val thingOrdered2 = new ThingOrdered(1, "y") thingOrdered2: ThingOrdered = ThingOrdered(1, y)
scala> val thingOrdered3 = new ThingOrdered(33, "b") thingOrdered3: ThingOrdered = ThingOrdered(33, b)
scala> val thingOrdered4 = new ThingOrdered(4, "m") thingOrdered4: ThingOrdered = ThingOrdered(4, m)
scala> val thingOrdered5 = new ThingOrdered(4, "n") thingOrdered5: ThingOrdered = ThingOrdered(4, n)
Now let’s compare the ThingOrdered
instances.
scala> thingOrdered1 > thingOrdered2 res3: Boolean = false
scala> thingOrdered1 < thingOrdered2 res4: Boolean = true
scala> thingOrdered1 == new ThingOrdered(1, "z") res5: Boolean = false
scala> thingOrdered1 == thingOrdered2 res6: Boolean = false
scala> thingOrdered1 <= thingOrdered2 res7: Boolean = true
scala> thingOrdered1 >= thingOrdered2 res8: Boolean = false
We can sort an Array
of ThingOrdered
using sorted
,
which returns a new sorted collection without modifying the original collection.
The Sorted
method is provided for all types of collections.
scala> val thingsOrdered = Array(thingOrdered1, thingOrdered2, thingOrdered3, thingOrdered4, thingOrdered5) thingsOrdered: Array[collections.ThingOrdered] = Array(ThingOrdered(1, x), ThingOrdered(1, y), ThingOrdered(33, b), ThingOrdered(4, m), ThingOrdered(4, n))]
scala> thingsOrdered.sorted res9: Array[collections.ThingOrdered] = Array(ThingOrdered(1, x), ThingOrdered(1, y), ThingOrdered(4, m), ThingOrdered(4, n), ThingOrdered(33, b))
Contrast this with Sorting.quickSort
, which modifies the Array
passed to it;
remember that Array
is a mutable data type.
scala> scala.util.Sorting.quickSort(thingsOrdered)
scala> thingsOrdered res10: Array[ThingOrdered] = Array(ThingOrdered(1, x), ThingOrdered(1, y), ThingOrdered(4, m), ThingOrdered(4, n), ThingOrdered(33, b))
Even if a natural ordering is defined for a collection of ThingOrdered
using Ordered
it is
still possible to invoke sortWith
to perform an ad-hoc sort.
As noted above this does not modify the original collection.
Here we see how to use sortWith
to sort according to two criteria, as implemented by two anonymous
Function2[Int, String, Boolean]
instances which perform the comparisons.
scala> thingsOrdered.sortWith((x, y) => x.i > y.i) res11: Array(ThingOrdered(33, b), ThingOrdered(4, m), ThingOrdered(4, n), ThingOrdered(1, x), ThingOrdered(1, y))
scala> thingOrdereds.sortWith((x, y) => x.s > y.s) res12: Array(ThingOrdered(1, y), ThingOrdered(1, x), ThingOrdered(4, n), ThingOrdered(4, m), ThingOrdered(33, b))
You can run this code example by typing:
$ sbt "runMain collections.CollectionOrdered"
Output is as shown above.
Ordering is Often Preferred to Ordered
Scala provides the Ordering
trait for objects that need multiple sorting strategies.
It is often better to use Ordering
instead of Ordered
because Ordered
must be implemented by the type to compare,
while with Ordering
you can define this ordering elsewhere.
To define the natural ordering, which is the default Ordering
instance for your type,
you just define an implicit ordering value in the companion object.
If you want a different ordering in a certain context, define an implicit or explicit Ordering
instance there.
We will see an example of this in a moment.
class ThingOrdering(val i: Int, val s: String) { override def toString = s"ThingOrdering($i, $s)" }
object ThingOrdering { implicit val defaultThingOrdering: Ordering[ThingOrdering] = Ordering.by { thing: ThingOrdering => (thing.i, thing.s) } }
As you can see, Ordering.by
receives a Function1[ThingOrdering, Ordering[ThingOrdering]]
.
Now let’s define some ThingOrdering
instances and put them into a collection called thingOrderings
.
scala> val thingOrdering1 = new ThingOrdering(1, "x") thingOrdering1: ThingOrdering = ThingOrdering(1, x)
scala> val thingOrdering2 = new ThingOrdering(1, "y") thingOrdering2: ThingOrdering = ThingOrdering(1, y)
scala> val thingOrdering3 = new ThingOrdering(33, "b") thingOrdering3: ThingOrdering = ThingOrdering(33, b)
scala> val thingOrdering4 = new ThingOrdering(4, "m") thingOrdering4: ThingOrdering = ThingOrdering(4, m)
scala> val thingOrdering5 = new ThingOrdering(4, "n") thingOrdering5: ThingOrdering = ThingOrdering(4, n)
scala> val thingOrderings = Array(thingOrdering1, thingOrdering2, thingOrdering3, thingOrdering4, thingOrdering5) thingOrderings: Array[ThingOrdering] = Array(ThingOrdering(1, x), ThingOrdering(1, y), ThingOrdering(33, b), ThingOrdering(4, m), ThingOrdering(4, n))
We can use the implicit Ordering
defined in the companion object to sort the collection of
ThingOrdering
according to its natural order using the ordered
method.
scala> thingOrderings.sorted res13: Array[ThingOrdering] = Array(ThingOrdering(1, x), ThingOrdering(1, y), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(33, b))
To show how Ordering
allows additional sort orderings beyond the natural sort ordering,
let’s define two sort schemes.
scala> val orderByI = Ordering.by { thing: ThingOrdering => thing.i } orderByI: scala.math.Ordering[ThingOrdering] = scala.math.Ordering$$anon$9@9083a57
scala> val orderByS = Ordering.by { thing: ThingOrdering => thing.s } orderByS: scala.math.Ordering[ThingOrdering] = scala.math.Ordering$$anon$9@27daad50
Now we can sort the thingOrderings
collection by each scheme.
scala> thingOrderings.sorted(orderByI) res14: Array(ThingOrdering(1, x), ThingOrdering(1, y), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(33, b))
scala> thingOrderings.sorted(orderByS) res15: Array[ThingOrdering] = Array(ThingOrdering(33, b), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(1, x), ThingOrdering(1, y))
You can run this code example by typing:
$ sbt "runMain collections.CollectionOrdering"
Output is as shown above.
Orderings Are Sensitive to Initialization Issues
This is not a problem for Scala 3, only for Scala 2.
Ordering
s defined in the constructor of a console App
cannot be imported into another
context because App
implements some unique initialization logic.
There is no compiler error or warning.
Here is a typical runtime error message, and I highlighted the portions of the stack trace that give a clue as to what the problem actually is.
Exception in thread "main" java.lang.ClassCastException: collections.ThingOrdering cannot be cast to java.lang.Comparable at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:316) at java.util.ComparableTimSort.sort(ComparableTimSort.java:184) at java.util.Arrays.sort(Arrays.java:1246) at java.util.Arrays.sort(Arrays.java:1433) at scala.collection.SeqLike$class.sorted(SeqLike.scala:618) at scala.collection.mutable.ArrayOps$ofRef.sorted(ArrayOps.scala:186) at collections.CollectionOrdering$.delayedEndpoint$collections$CollectionOrdering$1(CollectionSorting.scala:26) at collections.CollectionOrdering$delayedInit$body.apply(CollectionSorting.scala:21) at scala.Function0$class.apply$mcV$sp(Function0.scala:40) at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12) at scala.App$$anonfun$main$1.apply(App.scala:76) at scala.App$$anonfun$main$1.apply(App.scala:76) at scala.collection.immutable.List.foreach(List.scala:381) at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35) at scala.App$class.main(App.scala:76) at collections.CollectionOrdering$.main(CollectionSorting.scala:21) at collections.CollectionOrdering.main(CollectionSorting.scala)
We will see this initialization issue again with concurrency in later lectures.
Please ensure that you either define Ordering
s in an object that does not extend App
, or you define them in a trait or a class.
Defining Orderings
Because I want to reuse orderByI
and orderByS
for this section,
I had to define them in the collections package object
.
I placed all of the Ordering
s there for consistency, and to emphasize best practices.
val orderByI: Ordering[ThingOrdering] = Ordering.by { _.i } // cannot import if defined in App val orderByS: Ordering[ThingOrdering] = Ordering.by { _.s } // cannot import if defined in App val orderByIReverse: Ordering[ThingOrdering] = orderByI.reverse val orderBySReverse: Ordering[ThingOrdering] = orderByS.reverse val orderBySandI: Ordering[ThingOrdering] = Ordering.by { x => (x.s, x.i) } val orderByIandS: Ordering[ThingOrdering] = Ordering.by { x => (x.i, x.s) }
Notice that I defined four other Ordering
s.
-
orderByIReverse
andorderBySReverse
are transformedOrdering
s such that they sort in the reverse order oforderByI
andorderByS
. -
orderBySandI
andorderByIandS
sort by two keys, expressed asTuple2
s: the former sorts bys
as the primary key, andi
as the secondary key, and the latter does the opposite.
Sort results for all of these Ordering
s are.
scala> thingOrderings.sorted(orderByI) res16: Array[ThingOrdering] = Array(ThingOrdering(1, x), ThingOrdering(1, y), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(33, b))
scala> thingOrderings.sorted(orderByIReverse) res17: Array[ThingOrdering] = Array(ThingOrdering(33, b), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(1, x), ThingOrdering(1, y))
scala> thingOrderings.sorted(orderByS) res18: Array[ThingOrdering] = Array(ThingOrdering(33, b), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(1, x), ThingOrdering(1, y))
scala> thingOrderings.sorted(orderBySReverse) res19: Array[ThingOrdering] = Array(ThingOrdering(1, y), ThingOrdering(1, x), ThingOrdering(4, n), ThingOrdering(4, m), ThingOrdering(33, b))
scala> thingOrderings.sorted(orderBySandI) res20: Array[ThingOrdering] = Array(ThingOrdering(33, b), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(1, x), ThingOrdering(1, y))
scala> thingOrderings.sorted(orderByIandS) res21: Array[ThingOrdering] = Array(ThingOrdering(1, x), ThingOrdering(1, y), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(33, b))
scala> thingOrderings.sorted(orderBySandI.reverse) res22: Array[ThingOrdering] = Array(ThingOrdering(1, y), ThingOrdering(1, x), ThingOrdering(4, n), ThingOrdering(4, m), ThingOrdering(33, b))
scala> thingOrderings.sorted(orderByIandS.reverse) res23: Array[ThingOrdering] = Array(ThingOrdering(33, b), ThingOrdering(4, n), ThingOrdering(4, m), ThingOrdering(1, y), ThingOrdering(1, x))
math.Ordering.Implicits
Importing math.Ordering.Implicits._
makes 3 methods implicitly available:
-
infixOrderingOps
– allows you to write infix operators between instances that you are comparing that do not provide those operators. The operator are:<
,>
,<=
,>=
,==
and!=
. This import also defines additional implicit orderings, in particularOrdering
s ofComparable
implementations. -
seqOrdering
– can cause problems with ordering, import with caution. If you just want to importinfixOrderingOps
, I suggest you just write:Scala codeimport math.Ordering.Implicits.infixOrderingOps
-
sortedSetOrdering
– invokes the same implementation asseqOrdering
, but applies theOrdering
to aSortedSet
instead of aSeq
. The same cautionary message applies tosortedSetOrdering
.
scala> (1, "b") < (1, "a") <console>:8: error: value < is not a member of (Int, String) (1, "b") < (1, "a") ^
scala> import math.Ordering.Implicits.infixOrderingOps import math.Ordering.Implicits.infixOrderingOps
scala> (1, "b") < (1, "a") res1: Boolean = false
Total Orderings
New for Scala 2.13 is
scala.
and
Ordering.
.
I’ve paraphrased the Scaladoc, combining the two descriptions into one.
An ordering for
Float
s/Double
s which is a fully consistent total ordering, and treatsNaN
as larger than all otherFloat
/Double
values; it behaves the same asjava.
.lang. Float/Double. compare() Because the behaviour of
Float
s/Double
s specified by IEEE is not consistent with a total ordering when dealing withNaN
, there are two orderings defined forFloat
/Double
:TotalOrdering
, which is consistent with a total ordering, andIeeeOrdering
, which is consistent as much as possible with IEEE spec and floating point operations defined inscala.math
.These orderings may be preferable for sorting collections.
enum Value Sets
As we learned in the Enumerations lecture of the Introduction to Scala course, Java enums can be used to great benefit by Scala programs.
Here is a Java enum
stored in a file called src/main/java/Day.java
.
It is similar to Java 8’s
DayOfWeek
enum
.
public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }
Here is how we can obtain a Set[Day]
containing all values of Day
.
scala> val valueSet = Day.values.toSet valueSet: scala.collection.immutable.Set[Day] =Set(TUESDAY, SATURDAY, WEDNESDAY, MONDAY, FRIDAY, SUNDAY, THURSDAY)
Java Enum
implements
Comparable
.
Ordinarily it is awkward to compare two Java enum
s.
scala> Day.MONDAY.compareTo(Day.FRIDAY) res4: Int = -4
scala> Day.MONDAY.compareTo(Day.MONDAY) res5: Int = 0
scala> Day.FRIDAY.compareTo(Day.MONDAY) res6: Int = 4
After importing infixOrderingOps
you can compare two Day
s using infix notation and the
comparison operators that infixOrderingOps
provides.
scala> import math.Ordering.Implicits.infixOrderingOps import scala.math.Ordering.Implicits.infixOrderingOps
scala> Day.MONDAY < Day.FRIDAY res3: Boolean = true
What’s more, if we import all the values of Day
, like this:
scala> import Day._ import Day._
Then we can write:
scala> MONDAY < FRIDAY res4: Boolean = true
We can use this fact and the imported implicit Ordering
for Comparable
to create a
SortedSet
of the values.
Recall that a collection’s elements are unpacked and supplied as arguments to a method by following the collection name with :_*
.
scala> val values: collection.immutable.SortedSet[Day] = collection.immutable.TreeSet(Day.values:_*) values: scala.collection.immutable.SortedSet[Day] = TreeSet(SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY)
PriorityQueue
Now that we have learned about Ordering
, we can discuss the
PriorityQueue
mutable collection type, left over from the
Mutable Collections lecture.
Let’s remind ourselves of the contents of thingOrderings
,
sorted according to the natural Ordering
of ThingOrdering
.
scala> thingOrderings.sorted.mkString(", ") res0: String = ThingOrdering(1, x), ThingOrdering(1, y), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(33, b)
If we make a PriorityQueue
of ThingOrderings
, the implicit Ordering
in the
ThingOrderings
companion object is used to determine the order of the items to dequeue.
Notice that once pq1
is created, its contents are displayed in the reverse of the natural Ordering
.
This is because PriorityQueue
returns the highest priority item, which is the item that sorts into first position.
scala> val pq1 = mutable.PriorityQueue(thingOrderings:_*) pq1: scala.collection.mutable.PriorityQueue[collections.ThingOrdering] = PriorityQueue(ThingOrdering(33, b), ThingOrdering(4, n), ThingOrdering(1, y), ThingOrdering(1, x), ThingOrdering(4, m))
Now I’ll just keep pulling items from the PriorityQueue
instance by calling pq1.dequeue()
until NoSuchElementException
is thrown.
Items are returned in the reverse of the natural Ordering
.
scala> pq1.dequeue() res1: collections.ThingOrdering = ThingOrdering(33, b)
scala> pq1.dequeue() res2: collections.ThingOrdering = ThingOrdering(4, n)
scala> pq1.dequeue() res3: collections.ThingOrdering = ThingOrdering(4, m)
scala> pq1.dequeue() res4: collections.ThingOrdering = ThingOrdering(1, y)
scala> pq1.dequeue() res5: collections.ThingOrdering = ThingOrdering(1, x)
scala> pq1.dequeue() java.util.NoSuchElementException: no element to remove from heap at scala.collection.mutable.PriorityQueue.dequeue(PriorityQueue.scala:139) ... 43 elided
There are other ways to dequeue all the elements without throwing an Exception
, of course, which we will see in a moment.
You can explicitly specify the Ordering
when creating the PriorityQueue
.
This code example first reminds us what the contents of thingOrderings
looks like when sorted according to orderByI
.
We then create pq2
using orderByI
instead of the natural ordering of ThingOrdering
to prioritize the queue.
We then see one way of retrieving all of the elements from the PriorityQueue
in the reverse
Ordering
defined by orderByI
.
The foreach
expression iterates over the collection and prints out each item in order.
scala> thingOrderings.sorted(orderByI).mkString(", ") res7: String = ThingOrdering(1, x), ThingOrdering(1, y), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(33, b)
scala> val pq2 = mutable.PriorityQueue(thingOrderings:_*)(orderByI) pq2: scala.collection.mutable.PriorityQueue[collections.ThingOrdering] = PriorityQueue(ThingOrdering(33, b), ThingOrdering(4, m), ThingOrdering(1, x), ThingOrdering(1, y), ThingOrdering(4, n))
scala> pq2.dequeueAll.foreach { item => println(s"""pq2.dequeue()=$item""") } pq2.dequeue()=ThingOrdering(33, b) pq2.dequeue()=ThingOrdering(4, n) pq2.dequeue()=ThingOrdering(4, m) pq2.dequeue()=ThingOrdering(1, x) pq2.dequeue()=ThingOrdering(1, y)
This code example uses the orderByS
Ordering
to define the PriorityQueue
’s behavior.
All of the items are individually dequeued in a foreach
loop.
scala> thingOrderings.sorted(orderByS).mkString(", ") ThingOrdering(33, b), ThingOrdering(4, m), ThingOrdering(4, n), ThingOrdering(1, x), ThingOrdering(1, y)
scala> val pq3 = mutable.PriorityQueue(thingOrderings:_*)(orderByS) pq3: scala.collection.mutable.PriorityQueue[collections.ThingOrdering] = PriorityQueue(ThingOrdering(1, y), ThingOrdering(1, x), ThingOrdering(33, b), ThingOrdering(4, m), ThingOrdering(4, n))
scala> (0 until pq3.length).foreach { _ => println(s"""pq3.dequeue()=${pq3.dequeue()}""") } pq3.dequeue()=ThingOrdering(1, y) pq3.dequeue()=ThingOrdering(1, x) pq3.dequeue()=ThingOrdering(4, n) pq3.dequeue()=ThingOrdering(4, m) pq3.dequeue()=ThingOrdering(33, b)
Discovering the Default Implicit Ordering with Implicitly (Supplemental)
This only applies to Scala 2, not Scala 3.
As we saw in the lecture on Implicit Conversions,
the implicitly
keyword returns the implicit value in scope for a given type.
Let’s use the default implicit Ordering
(defined in the
scala package object
,
so it is available to all programs) to compare two tuples.
scala> val ordering = implicitly[Ordering[(Int, String)]] ordering: Ordering[(Int, String)] = scala.math.Ordering$$anon$11@12e6ca10
scala> ordering.compare( (1, "b"), (1, "a") ) res22: Int = 1
scala> ordering.compare( (1, "b"), (1, "b") ) res23: Int = 0
scala> ordering.compare( (1, "b"), (1, "c") ) res24: Int = -1
scala> implicitly[Ordering[Day]].compare(Day.MONDAY, Day.WEDNESDAY) res25: Int = -2
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.