Is there any built-in comparison function on primitives that returns `:lt`, `:eq` or `:gt`?

Is there any built-in comparison function on two numbers / strings / booleans that returns :lt, :eq or :gt?

No, only indirectly here: myers

Also Date and Time compare return those: Date — Elixir v1.14.0

3 Likes

Also Version.compare/2 and Logger.compare_levels/2.

I think such atoms are not needed for types that can be compared directly using term order.

2 Likes

When both data (only integers, strings, booleans, and datetime) and comparison rules are given by end-users, I think it would be convenient to have a consistent API.

can be easily defined and refined by the end user, e.g

def compare(a, b) do
  cond do
    a < b -> :lt
    a > b -> :gt
    true -> :eq
  end
end

too much niche depending on the implementation and use case, some may argue that the function needs to be in the view of the second argument not first, some may say that it needs to return -1, 0, 1

I know I can define that. Actually, that’s exactly what I did. I’m facing 3 conventions now:

  • DateTime.compare/2 returns :lt, :eq and :gt.
  • Timex.compare/2 returns -1, 0, 1.
  • for data types other than date and time, there’s no compare/2 to use. I have to use >, <, ==, etc. and work on booleans.

It would be better that Elixir standard lib provides a unified API.

1 Like
defprotocol Comparable do
  def compare(a, b)
end

defimpl Comparable,
  for: [Tuple, Atom, List, BitString, Integer, Float, Function, PID, Map, Port, Reference] do
  def compare(a, b) when a < b, do: :lt
  def compare(a, b) when a == b, do: :eq
  def compare(a, b) when a > b, do: :gt
end

Usage:

iex(1)> Comparable.compare(1, 2)
:lt
iex(2)> Comparable.compare("z", "a")
:gt
iex(3)> Comparable.compare([{:foo, "bar"}], foo: "bar")
:eq
5 Likes

:+1:

To have everything:

defimpl Comparable,
  for: [Date, Time, NaiveDateTime, DateTime] do
    defdelegate compare(a, b), to: @for
end
defimpl Comparable, for: [Timex] do
  # TODO
end
Comparable.compare(1, 2)
Comparable.compare(~D[2016-04-16], ~D[2016-04-28])
Comparable.compare(~T[16:04:16], ~T[16:04:28])
Comparable.compare(~N[2016-04-16 13:30:15], ~N[2016-04-28 16:19:25])

@for in defimpl

2 Likes

Or: defdelegate compare(a, b), to: @for

3 Likes

yes better, changed it.

2 Likes

I’d change the module to Ord and the function to cmp – I really like Rust’s API for it that much – but otherwise: excellent job! Hasn’t occurred to me to use protocols at all, I kind of avoid them like the plague.

Pretty elegant and nice, really like it.

1 Like

Elixir didn’t introduce a protocol, but it already kinda standardized compare/2 to return :lt | :eq | :gt for passing a module to sorting functions of Enum with elixir 1.10: Enum — Elixir v1.14.0

Decimal had to do a mayor version update for the breaking change moving from -1 | 0 | 1 to :lt | :eq | :gt. It seems like Timex didn’t do so.

5 Likes