# Orderable
Orderable is a simple Elixir package that allows you to make your custom data types orderable, so you can:
- Compare them
- Sort them
## Installation
The package can be installed
by adding `orderable` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:orderable, "~> 0.2.0"}
]
end
```
## Documentation
Documentation can be found at [https://hexdocs.pm/orderable](https://hexdocs.pm/orderable).
## How to write an implementation for my datatype?
There is only a single function to implement: `Orderable.ordered/1`.
This function should return a datatype that can be used with Erlang's built-in ordering to decide which is 'first'.
In general, use:
- Integers if you have a predefined, strict order.
- Floats if you have some calculations that make it difficult or impractical to work with integers.
- Strings/symbols will be ordered alphabetically (unicode-codepoint ordering).
Tuples can be used to return an ordering whose second element only is considered if the first element is the same (and the third is only used if the second elements are the same, etc.)
So if you have a struct `%Address{city: String.t, street: String.t, house_number: integer}` that you'd like to sort, you could implement it as follows:
defimpl Orderable, for: %Address do
def ordered(address) do
Orderable.ordered({address.city, address.street, address.house_number})
end
end
This will sort by city, and if they match by street, and if they match by house number.
Note also that we call `Orderable.ordered` recursively on the return value.
While this is only required if you know you might have custom values inside your data structur
(for which Orderable might also be implemented), it is considered good style, so you do not forget it later on when your data model changes.
For lists and tuples, Orderable.ordered will be called for each of the elements automatically.
## Full-scale Example
An example of a custom data structure that you'd want to order:
defmodule Rating do
defstruct [:subject, :rating, :user]
@ratings ~w{bad mediocre neutral good superb}a
def new(subject, rating, user), do: %__MODULE__{subject: subject, rating: rating, user: user}
@ratings_indexes @ratings |> Enum.with_index |> Enum.into(%{})
@doc false
def ratings_indexes do
@ratings_indexes
end
defimpl Orderable do
def ordered(rating) do
Orderable.ordered({rating.subject, Rating.ratings_indexes[rating.rating], rating.user})
end
end
end
Now, you can use it as follows:
iex> rating1 = Rating.new("Elixir", :superb, "Qqwy")
iex> rating2 = Rating.new("Erlang", :good, "Qqwy")
iex> rating3 = Rating.new("Peanut Butter", :bad, "Qqwy")
iex> rating4 = Rating.new("Doing the Dishes", :neutral, "Qqwy")
iex> rating5 = Rating.new("Elixir", :superb, "Nobbz")
iex> rating6 = Rating.new("Elixir", :neutral, "Timmy the Cat")
iex> unsorted = [rating1, rating2, rating3, rating4, rating5, rating6]
iex> unsorted |> Enum.sort_by(&Orderable.ordered/1)
[
%Rating{rating: :neutral, subject: "Doing the Dishes", user: "Qqwy"},
%Rating{rating: :neutral, subject: "Elixir", user: "Timmy the Cat"},
%Rating{rating: :superb, subject: "Elixir", user: "Nobbz"},
%Rating{rating: :superb, subject: "Elixir", user: "Qqwy"},
%Rating{rating: :good, subject: "Erlang", user: "Qqwy"},
%Rating{rating: :bad, subject: "Peanut Butter", user: "Qqwy"}
]
## default protocol implementations
Default implementations exist for all datatypes that are common to be compared.
For the following primitive values, calling `Orderable.ordered` is a no-op:
- Integers
- Floats
- Binaries
- Atoms
For the following ordered collection types, Orderable.ordered will automatically be called for each element:
- Lists
- Tuples
There are no default implementations for Maps, MapSets and other set-like things,
because these datatypes are only partially ordered (when using the common subset-based way of ordering things.)
There are also no default implementations for Functions, Pids, Ports and References,
because those are not things we usually order in a _semantical_ way (They are part of the built-in Erlang ordering only
so they can immediately be used as unique keys inside dictionary-like datatypes).