*1. Should it only compare structs of the same kind:*

I believe that when only comparisons between the same kind of data type 
(structs or built-in) are needed, a MyDatatype.compare function would be 
enough, which would mean that the core language does not need to be changed.

However:
*2. Or should it compare across types.*
I think that this is much more useful. Some examples:
- Comparisons between different kinds of dates and times, as is already the 
case right now.
- Comparisons between different kinds of numeric data types (built-in 
integers, built-in floats, decimals, rationals, etc)
- Comparisons between different kinds of data types representing parts of a 
hierarchy. (An example: Chain of command)

Of course, it is possible 'fake' some of these behaviours by wrapping the 
different types in a single struct, and define a single-type comparison for 
that one. (For instance: Instead of a General, Colonel and Sergeant struct, 
have a single 'Soldier' struct that contains details about rank in another 
way.) But this means that when comparing two data types, both first need to 
be wrapped, so I think this is not desirable.


*how would the protocol dispatch work. Should we sort both elements and 
dispatch the first as first?*
A comparison (possibly across types) is an operation working on an ordered 
pair of datatypes.

To make the dispatching underwater work, we could do one of the following:
I see two possibilities:
a) At runtime, sort the element module names (for built-in types, these are 
the 'fake' module names Integer, Float, etc. that are also used to define 
`defprotocol` and friends underwater) and dispatch on the first.
b) At compiletime, look at what way around the comparison is defined, and 
automatically define the other way around as its converse.

Advantage of a) is smaller code size, at the drawback of slower execution. 
b) has twice the code size, but is probably faster.

For the user defining the custom comparison implementation this does not 
really matter. 
What does matter, is that for the end user this is a 'protocol 
implementation' working with *two* datatypes instead of one. This means 
that, in one way or other, a way to specify this kind of 'protocol' 
implementation needs to be possible. Probably the easiest way (which is 
included in the example implementation 
<https://github.com/Qqwy/elixir_experimental_comparable> that I constructed 
together with @OpenmindDL1) would be to add a macro like `defcomparable 
FirstType SecondType do ... end` where there _has_ to be an implementation 
(using one or multiple clauses) of a function named 'compare'.
There might be other ways  to do this. If someone has another idea, please 
do tell.
One possibility might be something akin to Clojure's multimethod approach 
(basically generic 'Protocol' dispatching but possibly on other or multiple 
arguments instead of only the first) but this introduces a whole layer of 
additional complexity, which I think should best be avoided in the spirit 
of KISS.

*Note that Date.compare, Time.compare, and friends changed on Elixir master 
and are now able to compare across types. It does a field-based comparison 
instead of a struct-based comparison. For example, Date.compare/2 can 
compare any type that contains the fields year, month and day.*

I just looked at the source of this because I was curious how it was 
implemented. Maybe it is important to note that for Date, it is also 
required that the `calendar` key is set to match `Calendar.Iso`, and 'any 
type' means 'any custom type built on top of a map'.
It is great that these comparison functions have been added. What it does 
not solve (and that is something this proposal might change) is what should 
happen when the second date is from a different calendar.

The field-based lookup is definitely better than a structname-based lookup, 
but this will only work if the different kind of data structures happen to 
use the same names for the same values. This is very similar to 'duck 
typing' in a way. For instance, I could make a %Duration{hours:, minutes: 
seconds:} struct. It would be possible to use `Time.compare(myDuration)` 
but of course this is a nonsensical calculation.

Another problem would be that the system breaks down if we have a cyclic 
relationship: Take Rock, Paper, Scissors. Regardless of if they are part of 
the same type or different types, we want Rock to beat (be greater than) 
Scissors, Paper to beat Rock, and Scissors to beat Paper. If the only 
possibility would be to specify some fields that are transformed into a 
tuple which is then compared using Erlang's built-in term ordering, we are 
stuck.
Or what if we want to compare two things that can only be compared once a 
complicated calculation is performed using the data that is stored inside 
the struct? A struct might contain only a filename, but represent the file 
itself.

I think a better approach would be to allow the specification of a 
comparison implementation as one or multiple function clauses of the 
`compare` function.
This indeed has the implication that it will be impossible to use `compare` 
in guards (even if map-field access becomes possible), but I believe that 
this freedom is necessary, because of the restrictions outlined above. 



On Tuesday, November 22, 2016 at 9:28:51 AM UTC+1, José Valim wrote:
>
> We should continue the discussion here and include the implementation you 
> have so far. I believe implementation wise, the two main questions were:
>
> 1. Should it only compare structs of the same kind
>
> 2. Or should it compare across types. If comparing across types, how would 
> the protocol dispatch work. Should we sort both elements and dispatch the 
> first as first?
>
> Note that Date.compare, Time.compare, and friends changed on Elixir master 
> and are now able to compare across types. It does a field-based comparison 
> instead of a struct-based comparison. For example, Date.compare/2 can 
> compare any type that contains the fields year, month and day.
>
> When it comes to adding it to Elixir, the main drawback was that we didn't 
> agree it added enough to the language to warrant its inclusion in Elixir. 
> In particular, if the feature feels detached from Elixir overall, i.e. it 
> is just a new function+protocol that doesn't actually integrate with Elixir 
> features, such as guards, then it is probably best to leave it as a 
> library. Especially because it may affect future decisions in the language.
>
> For example, imagine we can access map fields or write case expressions in 
> guards. Would that allow us to write a guard-able compare expression? How 
> that would affect the compare implementation?
>
> Those are the same reasons why we decided to not include Decimal as part 
> of the standard library, it's why we don't provide an Array/Vector/Queue, 
> etc. If it is limited in scope it is probably best to leave it apart so we 
> still have the option to provide something truly integrated later on.
>
>
> *José Valim*
> www.plataformatec.com.br
> Skype: jv.ptec
> Founder and Director of R&D
>
>

-- 
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/4ef131cf-49a8-4fd6-93dd-cd68ec0f3f87%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to