rescript-core
rescript-core copied to clipboard
About Ordering module
I see there is a new Ordering
module that defines the return type for ordering functions. I'm wondering if we would get more flexibility by defining Ordering.t
as the comparison function (not just the return value) similar to the code below and as seen in https://github.com/gcanti/fp-ts/blob/master/src/Ord.ts
type t<'a> = ('a, 'a) => int // (or float)
let eval = (t, x, y) => t(x, y)
let map = (f, t, x, y) => eval(t, f(x), f(y))
let fromFloat = (cmp, x, y) => cmp(x, y)->Belt.Int.fromFloat
let reverse = (t, x, y) => eval(t, y, x)
let eq = (t, x, y) => eval(t, x, y) === 0
let neq = (t, x, y) => eval(t, x, y) !== 0
let lt = (t, x, y) => eval(t, x, y) < 0
let lte = (t, x, y) => eval(t, x, y) <= 0
let gt = (t, x, y) => eval(t, x, y) > 0
let gte = (t, x, y) => eval(t, x, y) >= 0
let min = (t, x, y) => lte(t, x, y) ? x : y
let max = (t, x, y) => lte(t, x, y) ? y : x
let clamp = (t, low, high) => v => min(t, high, max(t, low, v))
The primary consumers of these comparison functions are (1) Array.sort
, and (2) constructing a Belt Set and Map. Here's how you could use it. I think this approach makes it easier to sort/compare objects by specific properties because it is easier to construct a comparison function via map
. Once you have a Ordering.t
function, it becomes trivial to modify it with reverse
or use it with pairs of items with min
, max
, gte
, neq
. The Belt data structures need an int returning function and we can provide a converter in this module so functions that return a float can be used there as well.
module Person = {
type person = {
name: string,
age: int,
}
let getAge = i => i.age
}
// Ordering.t is a function
let youngestFirst = Ordering.map(Person.getAge, Int32.compare)
let oldestFirst = Ordering.map(Person.getAge, Int32.compare)->Ordering.reverse
let sortPeopleOldestFirst = people => people->Js.Array2.sortInPlaceWith(oldestFirst)
let oldestOfTwo = (a, b) => Ordering.max(oldestFirst, a, b)
// Other way
let youngestFirst2 = (a, b) => Int32.compare(a->Person.getAge, b->Person.getAge)
let sortPeopleYoungestFirst = people => people->Js.Array2.sortInPlaceWith(youngestFirst2)
let youngestOfTwo = (a, b) => Int32.compare(a->Person.getAge, b->Person.getAge) <= 0 ? a : b
Summary of benefits of this approach
- We provide various built-in comparison functions like
String.compare
,Date.compare
, andFloat.compare
. But the moment you want to sort in reverse, you need to write your own function. It is simpler to do something likeString.compare->Ordering.reverse
to tweak/modify the existing function. This gives you a function you can easily plug intoArray.sort
and other places. - There are libraries that require a comparison function that returns an int, like when building a Belt Set or Map. It is easier to tweak/modify the existing function to make it work in these contexts; just call
Date.compare->Ordering.toInt
- It is extremely common to want to sort an object based on a projection/key like in my example above for sorting people by age. It is convenient to call a function like
Ordering.map
to construct a new comparison function. This is supported by the TypeScript fp-ts library and is similar to the RustmaxByKey
function - https://doc.rust-lang.org/std/cmp/trait.Ord.html. - Once you have a comparison function, there are many useful things to do with it that are not supported by the current Ordering module. Things like
min
,max
,clamp
,gt
,gte
,eq
, etc.