Michał.
--
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 elixir-lang-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/836EAED5-CCA7-4144-A14B-E9637140A55C%40muskala.eu.
For more options, visit https://groups.google.com/d/optout.
Is there any requirement for Comparable to be in elixir core? Could this be implemented as a library?
--
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 elixir-lang-co...@googlegroups.com.
> standalone protocols are a mildly bad idea.
I'd love for some exploration of solutions to the painpoints of protocols. That may be a separate discussion but it seems like if that's the status quo then protocols need to be revisited. That's not a knock against the current implementation of protocols, but perhaps there's a lesson to be learned and productive changes that can be made in the future.
--
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 elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/70019F09-3148-4031-8DFF-E674571A7F24%40muskala.eu.
Finally the function wouldn’t work in guards, but that’s true of most of custom functions that are lacking natively in BEAM.
Yes. I see big benefit for that function with native calendar types that ship with elixir. Including it in core makes sense in that regard. Furthermore, standalone protocols are a mildly bad idea.
--
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 elixir-lang-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4LyduWvoTKS6wmxrzupEPUqxNt7vdRFjWGTmk5yyc83sw%40mail.gmail.com.
why not use -1, 0, 1 instead of :lt, :eq, :gt?
def lt?(a, b) do
compare(a, b) < 0
end
Is there any requirement for Comparable to be in elixir core? Could this be implemented as a library?
# pseudocode
case compare(a, b) do
-1 -> implies compare(b, a) == 1
0 -> implies compare(b, a) == 0
1 -> implies compare(b, a) == -1
end
Comparable do
# ... fill in non-dispatching implementations for built-in types here.
def compare(a = %type_a{}, b = %type_b{}) when type_a <= type_b do
Comparable.Protocol.compare(a, b)
end
def compare(a = %type_a{}, b = %type_b{}) do
Comparable.Protocol.compare(a, b) * -1
end
end
--
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 elixir-lang-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/b31aec7d-6bb0-4afa-ae88-9adfa6aa4537%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
# Example structs defmodule Bike do defstruct gears: 5 end defmodule Car do defstruct top_speed: 80 end defmodule Truck do defstruct wheels: 4 def compare(%{wheels: wheels0}, %{wheels: wheels1}), do: Comparable.compare end # An example of what a macro could generate given some information that describes things: defmodule Comparable do def compare(t, t), do: :eq # Could generalize this call to something like: # def compare(%{__struct__: type}=t0, %{__struct__: type}=t1), do: type.compare(t0, t1) # However that catches structs that do not support a compare and has a speed overhead due to dynamic dispatch, so I would not recommend it def compare(%Bike{gears: gears0}, %Bike{gears: gears1}), do: compare(gears0, gears1) def compare(%Bike{}, %Car{}), do: :lt def compare(%Bike{}, %Truck{}), do: :lt def compare(%Car{top_speed: top_speed0}, %Car{top_speed: top_speed1}), do: compare(top_speed0, top_speed1) def compare(%Car{}, %Bike{}), do: :gt def compare(%Car{}, %Truck{}), do: :lt def compare(%Truck{wheels: wheels0}, %Truck{wheels: wheels1}), do: compare(wheels0, wheels1) def compare(%Truck{}, %Bike{}), do: :gt def compare(%Truck{}, %Car{}), do: :gt def compare(%{__struct__: _t0}, %{__struct__: _t1}, do: raise MatchError(blah) def compare(t0, t1), do: if(t0<t1, then: :lt, else: if(t0>t1, do: :gt, else: if(t0 == t1, do: :eq, else: raise MatchError(blah)))) end
Given the examples below, maybe it would be better to provide protocols for each of those cases. The protocol would convert those data types to an intermediate representation that would be easy to compare on.
def compare(%Bike{gears: gears0}, %Bike{gears: gears1}), do: compare(gears0, gears1)
This could indeed be faster, however you'd have to get everything to agree on a single representation (a special tuple format perhaps? but then how would that rank with other tuples?).
This could indeed be faster, however you'd have to get everything to agree on a single representation (a special tuple format perhaps? but then how would that rank with other tuples?).You don't have to agree on a format though. Imagine Decimal defines its own format and Rational defines another, as long as at some point you implement each other protocols, anywhere, it will work just fine. That's the beauty of protocols after all! In the worst scenario, it will degenerate to the case we are talking about right now: (n * (n - 1) / 2) implementations?
What we miss when doing a pre-compile step, is things like:{A} should be comparable with {B}, {B} should be comparable with {C}, but {A} and {C} cannot be compared in a meaningful way.I cannot think of a practical example right now, but I think it is a valid problem.
Also, when a third data type needs to be added to an existing intermediate representation, how can this be done? There will be cases in which this existing intermediate representation is no longer valid, but you're unable to change the protocol implementations of those other types, so you're stuck.
I also think that there are many cases in which it might be impossible to come up with a proper intermediate format. We have both Enum.sort_by (which uses a Schwarzian Transform, i.e. it first creates an intermediate representation for all data elements and then sorts based on these) and the normal Enum.sort. Although it usually is faster, there are cases where it is impractical or impossible to use Enum.sort_by.An example: What about cases like Rock, Paper Scissors? Rock < Paper, Paper < Scissors, Scissors < Rock? Although there is no complete order for these data structures, we do want to be able to compare individual pairs.
Another nice edge case that would go completely wrong with an intermediate-representation approach, would be NaN, which is unequal to everything else, including itself.
So, I think it is paramount to create a multiprotocol or multimethod-like approach that works on the relationship or combination of the two types being compared. On an edge of the graph, rather than on a node like normal protocols do. After all, a comparison is something that is about the relationship between two things. You cannot compare a single thing.
You could potentially sneak in some tricks though. If you use something like protocols (though something new, not protocol) to generate a call tree in a single module, a single entrance function that matches based on both of the types (properly ordered so the beam can efficiently match well) would get it down to log(n). Hmm, although if you have a *lot* of potential matches (and I mean a **LOT**) then you could dispatch into a map and that 'might' be faster (though honestly I would not bet on it). This would require a post-compile step like protocols do though (is there a pattern to do those kind of things with elixir or are protocols just 'special'?). For normal macro's the best you could really get is to manually specify everything in a single file in your base project, just import the other modules that define matchers, and have a macro build up the same call tree.
# Let's define our own things from earlier: defcompare Car.Compare do # Whatever namespace you want, let's just put each in their own .Compare defcompare (%Car{top_speed: top_speed0}, %Car{top_speed: top_speed1}), do: compare(top_speed0, top_speed1) defcompare (%Car{}, %Bike{}), do: :gt
defcompare (%Car{}, %Truck{}), do: :lt
end defcompare Bike.Compare do defcompare (%Bike{gears: gears0}, %Bike{gears: gears1}), do: compare(gears0, gears1) defcompare (%Bike{}, %Car{}), do: :lt
defcompare (%Bike{}, %Truck{}), do: :lt
end # Let's define trucks inline below, just because defcompare Compare do # Let's make a top-level Compare (no one should do this unless in a final project, non-library # Bring in other libraries if they have definitions, otherwise define them all inline defcompare Timex.Compare defcompare OtherStuff.Compare defcompare Car.Compare defcompare Bike.Compare # Truck inline, just for an example: defcompare (%Truck{wheels: wheels0}, %Truck{wheels: wheels1}), do: compare(wheels0, wheels1) defcompare (%Truck{}, %Bike{}), do: :gt
defcompare (%Truck{}, %Car{}), do: :gt
end
defcomparison(Integer, RomanNumeral) do | |
def compare(int, %RomanNumeral{num: num}) when num < int, do: :< | |
def compare(int, %RomanNumeral{num: num}) when num > int, do: :> | |
def compare(int, %RomanNumeral{}) , do: := | |
end |
defcomparison(Integer, RomanNumeral) do | |
def compare(int, %RomanNumeral{num: num}), do: Comparable.compare(int, num) | |
end |
defcomparison(Integer, %RomanNumeral{num: Integer}) do | |
def compare(int, %RomanNumeral{num: num}), do: Comparable.compare(int, num) | |
end |
defcomparison(int, %RomanNumeral{num: num}) when is_int(int) and is_int(num) do | |
def compare(int, %RomanNumeral{num: num}), do: Comparable.compare(int, num) | |
end |
defcomparison(int, %RomanNumeral{num: num}) when is_int(int) and is_int(num) do | |
Comparable.compare(int, num) | |
end |
defcomparison(int, %RomanNumeral{num: num}) when is_int(int) and is_int(num) do | |
Comparable.compare(int, num) | |
end defcomparison(float, %RomanNumeral{num: num}) when is_float(float) and is_int(num) do Comparable.compare(float, num) end |
defcomparison(v, %RomanNumeral{num: num}) when is_int(num) do | |
Comparable.compare(v, num) | |
end |
defcomparison(v, %RomanNumeral{num: num}) when is_int(num) and Comparable.compare(v, num) |
defclass Kernel.Eq do where do @operator == # Assuming there is a way to define an operator or so to call the next function, otherwise ignore lines like @operator eq?(any, any) :: boolean() end defproperty equals_self(e) do true == eq?(e, e) end end defclass Kernel.Ord do extend Kernel.Eq where do compare(any, any) :: boolean() end defproperty compare_test(l, r) do compare(l, r) == case compare(r, l) do :< -> :> :> -> :< := -> := end end @operator == @spec eq?(any, any) :: boolean def eq?(l, r), do: compare(l, r) == := # Implement Kernel.Eq's call @operator < @spec lt?(any, any) :: boolean def lt?(l, r), do: compare(l, r) == :< @operator <= @spec le?(any, any) :: boolean def le?(l, r), do: compare(l, r) in [:<, :=] @operator > @spec gt?(any, any) :: boolean def gt?(l, r), do: compare(l, r) == :> @operator >= @spec ge?(any, any) :: boolean def ge?(l, r), do: compare(l, r) in [:>, :=] # Etc... whatever other things you want to implement on this end # Some implementation! I changed the syntax here to be more generically useful definstance Kernel.Ord(l, r) when is_int(l) and is_int(r) do def compare(l, r) do cond do l<r -> :< l>r -> :> l===r -> := end end definstance Kernel.Ord(l, r) when is_float(l) and is_float(r) do def compare(l, r) do cond do l<r -> :< l>r -> :> l===r -> := end end definstance Kernel.Ord(l, r) when is_int(l) and is_float(r) do def compare(l, r) do cond do l<r -> :< l>r -> :> l==r -> := end end definstance Kernel.Ord(l, r) when is_float(l) and is_int(r) do defdelegate compare(l, r), to: Kernel.Ord(r, l) end # Thought honestly for basic types you'd probably just have a single Kernel.Ord instance # that tests for any basic type and compares appropriately, like via erlang term ordering # for everything except `is_struct`'s. Maybe like this: definstance Kernel.Ord(l, r) when not is_struct(l) and not is_struct(r) do def compare(l, r) do # Default to erlang term ordering for all non-struct types cond do l<r -> :< l>r -> :> l==r -> := # What to do about float/int comparisons by default, leave it loose like `==`, or tight like `===`? end end # A custom type defmodule MyDate do defstruct year: 0, month: 0, day: 0 end # Lets support comparing our date with any map/struct that has year/month/day keys! definstance Kernel.Ord(%MyDate{}, %{year: _, month: _, day: _}) when do def compare(d0, d1) do # delegate to the list compare because I'm lazy Kernel.Ord.compare([d0.year, d0.month, d0.day], [d1.year, d1.month, d1.day]) end end # And let's support both orders definstance Kernel.Ord(%{year: _, month: _, day: _}, %MyDate{}) when do def compare(d0, d1), do: Kernel.Ord.compare(d1, d0) end
```elixir<table class="highlight tab-size js-file-line-container" data-tab-size="8" style="border-collapse: collapse; box-sizing: border-box; tab-size: 8; font-family:
# 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 elixir-lang-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/a7e59ba9-699b-42db-a81f-cbda00179fc8%40googlegroups.com.
--
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 elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/c55bd3c0-a2a9-4955-a62f-9184a587a029%40googlegroups.com.
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.
--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 elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/a74bb762-546f-4856-8029-58fe48b30b30%40googlegroups.com.
I am not really following your reasoning here. It seems like you state that not being able to handle struct<->struct coercions is a flaw of the proposal, but then you go on saying that having an intermediate representation is always better, meaning that coercion in these cases is not relevant.
The main reason why I believe that this should be integrated into the language is for the same reason that the (Naive)(Date)(Time)-structs were made part of the language: Consistency and therefore Interoptability between libraries.
Ergo, this would mean that there would be a `comparable` library, and the 'compatibility-layer' libraries: `comparable_timex`, `comparable_decimal`, `comparable_ratio`, etc... And then finally libraries like `priority_queue` that are built on top of `comparable`. However, using these libraries that are built on top of`comparable` is not so nice, because the responsibility of making sure that the proper 'compatibility-layer' is loaded as a dependency shifts to the end user.
There are many algorithms and data structures that would want to work with a Logical Ordering (rather than the Erlang Term Ordering). Including but not limited to:- Sorting algorithms- Searching algorithms- Sets- Multisets- Maps- Multimaps- Trees (of which there are of course many shapes and kinds)- Graphs and their related structures.- Pathfinding algorithms.- Permutation manipulation algorithms.
When these structures use Structual Ordering, they will work differently from when using Logical Ordering.An example: If I want to keep a top ten of highscores, I only want to order %HighScore{}'s based on their score, and not on the user that archieved the score or on the time the score was arrived at. A 'custom ordering' is exactly what this proposal wants to allow to be made.
When these structures use Structual Ordering, they will work differently from when using Logical Ordering.An example: If I want to keep a top ten of highscores, I only want to order %HighScore{}'s based on their score, and not on the user that archieved the score or on the time the score was arrived at. A 'custom ordering' is exactly what this proposal wants to allow to be made.If you want custom ordering, then a protocol is not the solution because they are global. Imagine you have a collection of users. Sometimes you want to order them by age, sometimes by name. Sure, you can implement the Comparable protocol for %User{} in a way that makes sense most times, but that doesn't exclude the fact you still want custom ordering via functions.
In fact, using a ordering that does not work across types would be detrimental in Elixir, since we do allow a list to have any data type. For example, if Enum.sort/1 used the proposed protocol, it would be unable to work on heterogeneous collection by default. If tree data types used that as well, we would be unable to have heterogenous trees, etc.My point is: we already have an ordering mechanism. Sure, it has flaws, but there is little benefit in replacing it by a version that is slower and works only on homogenous collections. The answer is to continue supporting custom ordering functions. If we want to standardize the use of :eq | :lt | :gt, then let's do that by providing functions that work on those types. The answer is not a Comparable protocol.