Thank you for your reply, Michał.

You are right, it does not. I don't know why I was thinking that Erlang 
Term Ordering would be important for Enum.sort.

This means that it probably is best to use `:lt`, `:gt`, `:eq` as outcome, 
since that is what Elixir itself already uses.

For proper use with Enum.sort, we could, besides `Kernel.compare/2` create 
`Kernel.lt?`,`Kernel.lte?`, `Kernel.equal?``Kernel.gte?`, `Kernel.gt?`, 
although this becomes such a large number of functions that we might put it 
under their own namespace (`Compare`?) instead.

We could change `Enum.sort` to use `&Kernel.lte?/2` by default, rather than 
`&<=/2`. Although this might have slight efficiency implications for 
existing applications, I think it would uphold the Principle of Least 
Surprise better.


On Friday, June 9, 2017 at 1:34:47 PM UTC+2, Michał Muskała wrote:
>
> 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] 
> <javascript:>>, 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] <javascript:>.
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/elixir-lang-core/c55bd3c0-a2a9-4955-a62f-9184a587a029%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/elixir-lang-core/c55bd3c0-a2a9-4955-a62f-9184a587a029%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
> 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/ae813abb-05a1-4f01-983d-fa76bf760bf1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to