Just a quick remark (for anything more this requires a deeper analysis): Why would erlang term ordering be important for the return value of compare/2 function? The function you pass to Enum.sort should return true or false, so you can't use it there directly anyway.
Michał. On 9 Jun 2017, 13:21 +0200, Wiebe-Marten Wijnja <[email protected]>, wrote: > I would like to revive this topic, because I am still looking for a good way > to perform comparisons on custom structures. > > Right now there is no standardized way to do this, resulting in widely > varying APIs❧, many of which are incompatible with e.g. `Enum.sort` because > of the chosen output format, which would be one of the most obvious and > frequent use-cases of comparison operations. > > So, I would like to propose the following: > > > • Add a `Comparable` protocol (Or behaviour?), containing > `Comparable.compare(MyStruct.t, MyStruct.t)`. > • Choose a sensible output format for this compare function. This is > something we should discuss: > • > > • In existing parts of Elixir Core (the (Date)(Time) modules), `:lt`, > `:eq`, `:gt` are used. But these do not themselves follow the Erlang Term > Ordering, and therefore cannot be used directly for things > like`Enum.sort(enumerable, &Kernel.compare/2)`. > • Many other languages, and multiple existing libraries return an integer > `-1, 0, 1`. This does follow the Erlang Term Ordering. Main disadvantage: > They are not very descriptive, someone might think that any integer could be > returned. > • `:<`, `:=`, `:>` are another possibility: Because of the ASCII > ordering, these follow the Erlang Term Ordering, and are arguably more > descriptive than the integers. Main disadvantage: They are not widely used > yet. > • What to do on error? Raise? Or return `nil`? `{:error, some_reason}` is > another possibility (Although we do not use an `{:ok, success}` here, so > maybe this is confusing?). > > • Add `Kernel.compare/2`, which is overridden to use simple comparison for > built-in datatypes, and dispatches to the protocol for structs (raising when > the structs are not of the same type). > > Aside from this, (this can be accepted/rejected independently!) I see value > in a `Comparable.coerce(builtin)`, which can be optionally implemented to > allow a builtin datatype to be converted to the struct of the other, before > comparing. > > > > ❧ About widely-varying APIs: > > - Time.compare, Date.compare, DateTime.compare return `:lt`, `:eq` or `:gt` > - Timex.compare returns integer (-1, 0, 1) or {:error, reason} and has an > optional granularity. > - Decimal.compare returns #Decimal<-1>, #Decimal<0>, #Decimal<1> or > #Decimal<NaN>. > - Decimal.cmp returns `:lt`, :eq` or `:gt` and raises on NaN. > - Ratio.compare returns integer (-1, 0, 1) or raises Ratio.ComparisonError. > > > > > On Wednesday, December 14, 2016 at 12:49:26 PM UTC+1, Wiebe-Marten Wijnja > wrote: > > I have been thinking longer about this. > > > > I think that the across-type implementation is overkill; I have a lot of > > trouble to come up with cases where this would be useful: Cases that I can > > think of can readily be solved by wrapping the inner structure in a > > containing struct, which is obviously more clear/explicit than providing a > > two-typed frankenprotocol. > > > > > > I have been working on Numbers, which is basically dispatches arithmetic > > operations to any structs that implements its standardized Numeric > > behaviour. In this case, this means that functions/modules/structs can be > > written that wrap any kind of thing that implements the behaviour, which > > means that e.g. my Tensor library allows addition/multiplication to > > performed regardless of if the contents of the vectors/matrices/tensors are > > `Integer`s, `Float`s, `Decimal`s, `Ratio`nals or even `ComplexNum`bers. > > > > I think that such a standardized way constructing something still is very > > important in the core language, because a standardized API means that > > modules consuming the API can use any modules(/data types) that are > > exposing the API. > > > > I now envision the following, much simpler and less 'new language > > feature'-heavy than my original proposal: > > > > --------------------- > > > > There is a normal protocol called Comparable, exposing two functions that > > can be overridden: > > > > • compare(a, b) compares the two structs `a` and `b` of the same type. This > > function should return `:lt`, `:gt`, or `:eq` (keeping with the > > specification that DateTime.compare and Time.compare already follow). If > > there is no sensible way to compare the two types, a > > Comparable.CannotCompareError should be raised, with a describing error > > message. The Any implementation always raises this error. > > • coerce(some_builtin_value). Can optionally(!) be implemented to allow > > certain standard data types (e.g. numbers, or strings) to be automatically > > converted to the type of the thing we want to compare it with, to allow > > shorter notation for things like `compare(Decimal.new(2), 3)`. The Any > > implementation always raises Comparable.CannotCompareError. > > > > There is a new function in the Kernel function Kernel.compare(a, b) has the > > following variants: > > > > # struct <=> struct > > Kernel.compare(a = %someStruct{}, b = %someStruct{}), do: > > Comparable.compare(a, b) > > # struct <=> differentStruct > > Kernel.compare(a = %someStruct{}, b = %someDifferentStruct{}), do: raise > > Comparable.CannotCompareError, message: "Cannot compare #{inspect(a)} with > > #{inspect(b)}." > > # struct <=> builtin > > Kernel.compare(a = %someStruct{}, b), do: Comparable.compare(a, > > Comparable.coerce(b)) > > # builtin <=> struct > > Kernel.compare(a, b = %someStruct{}), do: > > Comparable.compare(Comparable.coerce(a), b) > > # builtin <=> builtin, use Erlang's built-in term ordering. > > Kernel.compare(a, b) when a == b, do: :eq > > Kernel.compare(a, b) when a < b, do: :lt > > Kernel.compare(a, b), do: :gt > > > > > > > > > > > > > > > > > -- > You received this message because you are subscribed to the Google Groups > "elixir-lang-core" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To view this discussion on the web visit > https://groups.google.com/d/msgid/elixir-lang-core/c55bd3c0-a2a9-4955-a62f-9184a587a029%40googlegroups.com. > For more options, visit https://groups.google.com/d/optout. -- You received this message because you are subscribed to the Google Groups "elixir-lang-core" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/6bc6ef28-ea74-45c4-99c4-d7ff4c0154e8%40Spark. For more options, visit https://groups.google.com/d/optout.
