stdlib icon indicating copy to clipboard operation
stdlib copied to clipboard

add an order function to help chain comparisons

Open schurhammer opened this issue 1 year ago • 2 comments
trafficstars

When writing a comparison function, a common pattern is to compare one thing, then if its equal compare another thing and so on. e.g.


fn compare_cat(a: Cat, b: Cat) -> Order {
  case string.compare(a.name, b.name) {
    Eq ->
      case int.compare(a.age, b.age) {
        Eq -> int.compare(a.cuteness, b.cuteness)
        x -> x
      }
    x -> x
  }
}

We could add some sort of "chain" function to make this nicer

fn compare_cat2(a: Cat, b: Cat) -> Order {
  string.compare(a.name, b.name)
  |> chain(int.compare(a.age, b.age))
  |> chain(int.compare(a.cuteness, b.cuteness))
}

fn chain(first: Order, then: Order) {
  case first {
    Eq -> then
    x -> x
  }
}

Not sure if this name and/or api is ideal, but I think a function similar to this would be helpful

schurhammer avatar Mar 05 '24 00:03 schurhammer

Hello! I remember feeling the need for something similar in the past, I called it break_ties, you can achieve something similar using bool.guard:

fn compare_cat2(a: Cat, b: Cat) -> Order {
  let name_order = string.compare(a.name, b.name)
  use <- bool.guard(when: name_order != Eq, return: name_order)
  // ^ if the name comparison is not `Eq`, we return it immediately
  //   otherwise we go on and compare the rest.
  let age_order = int.compare(a.age, b.age)
  use <- bool.guard(when: age_order != Eq, return: age_order)
  // ^ if the age comparison is not `Eq`, we return it immediately
  //   otherwise we go on and compare the rest
  int.compare(a.cuteness, b.cuteness)
  // ^ if everything else is equal, the comparison on `cuteness`
  //   will decide the outcome of the comparison
}

giacomocavalieri avatar Mar 05 '24 08:03 giacomocavalieri

Sounds useful! A lazy variant would be useful too.

I don't feel very strongly about the name in either direction. What other options do we have? Just see if there's anything better we can go with before this

lpil avatar Mar 06 '24 13:03 lpil

How about a lazy implementation named then to mirror Option.then and Result.then?

pub fn then(order: Order, compare: fn(a, a) -> Order, a1: a, to a2: a) -> Order {
  case order {
    Eq -> compare(a1, a2)
    _ -> order
  }
}

or, using bool.guard:

pub fn then(order: Order, compare: fn(a, a) -> Order, a1: a, to a2: a) -> Order {
  use <- bool.guard(when: order != Eq, return: order)
  compare(a1, a2)
}

Then we might compare cats like so:

pub fn compare_cat(a: Cat, b: Cat) -> Order {
  string.compare(a.name, b.name)
  |> order.then(int.compare, a.age, to: b.age)
  |> order.then(int.compare, a.cuteness, to: b.cuteness)
}

As an aside, I see that string.compare does not name its 2nd parameter, but int.compare names it with. Here, I've named it to (as I personally tend to use the phrase "compare to", not "compare with", but that's a personal preference). In any case all, of the compare functions should be consistent with each other.

chuckwondo avatar Mar 06 '24 14:03 chuckwondo

What's the advantage here of making it a higher order function over working with 3 orders?

lpil avatar Mar 06 '24 14:03 lpil

I think it'd be easier to use one that works directly with an order:

pub fn break_tie(in order: Order, with comparison: Order) -> Order {
  case order {
    Lt | Gt -> order
    Eq -> comparison
  }
}

pub fn lazy_break_tie(in order: Order, with comparison: fn() -> Order) -> Order {
  case order {
   Lt | Gt -> order
   Eq -> comparison()
  }
}

That could then be used like this:

// Lazy version that plays nicely with `use`
use <- order.lazy_break_tie(in: string.compare(a.name, b.name))
use <- order.lazy_break_tie(in: int.compare(a.age, b.age))
int.compare(a.cuteness, b.cuteness)

// Non lazy version
string.compare(a.name, b.name)
|> order.break_tie(with: int.compare(a.age, b.age))
|> order.break_tie(with: int.compare(a.cuteness, b.cuteness))

I'm not really sure what a good name would be here, I'm just going with break_tie out of habit :)

giacomocavalieri avatar Mar 06 '24 15:03 giacomocavalieri

break_tie sounds nice and descriptive to me.

I wonder if there's another word that means this. Perhaps in the context of a competition or tournament. I can't think of anything right now

lpil avatar Mar 06 '24 15:03 lpil