mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
improvement: remove :comparable
as a dependency
It all compiles conditionally, so that if an explicit dependency exists on `comp` it will still be used
This commit is contained in:
parent
e94e484885
commit
58065c31cd
12 changed files with 493 additions and 22 deletions
|
@ -499,9 +499,6 @@ defmodule Ash.Actions.Read do
|
|||
end
|
||||
end)
|
||||
else
|
||||
%Ash.Query{} = query ->
|
||||
{{:error, query}, query}
|
||||
|
||||
{:ok, query} ->
|
||||
{{:error, query}, query}
|
||||
|
||||
|
|
|
@ -51,3 +51,9 @@ defmodule Ash.Type.Date do
|
|||
Ecto.Type.dump(:date, value)
|
||||
end
|
||||
end
|
||||
|
||||
import Ash.Type.Comparable
|
||||
|
||||
defcomparable left :: Date, right :: Date do
|
||||
Date.compare(left, right)
|
||||
end
|
||||
|
|
|
@ -110,3 +110,9 @@ defmodule Ash.Type.DateTime do
|
|||
Ecto.Type.dump(storage_type(constraints), value)
|
||||
end
|
||||
end
|
||||
|
||||
import Ash.Type.Comparable
|
||||
|
||||
defcomparable left :: DateTime, right :: DateTime do
|
||||
DateTime.compare(left, right)
|
||||
end
|
||||
|
|
|
@ -227,3 +227,21 @@ defmodule Ash.Type.Decimal do
|
|||
def new(%Decimal{} = v), do: v
|
||||
def new(v), do: Decimal.new(v)
|
||||
end
|
||||
|
||||
import Ash.Type.Comparable
|
||||
|
||||
defcomparable left :: Decimal, right :: Integer do
|
||||
Decimal.compare(left, Ash.Type.Decimal.new(right))
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: Decimal do
|
||||
Decimal.compare(left, right)
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: Float do
|
||||
Decimal.compare(Ash.Type.Decimal.new(left), right)
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: BitString do
|
||||
Decimal.compare(left, Ash.Type.Decimal.new(right))
|
||||
end
|
||||
|
|
|
@ -1,17 +1 @@
|
|||
import Ash.Type.Comparable
|
||||
|
||||
defcomparable left :: Decimal, right :: Integer do
|
||||
Decimal.compare(left, Ash.Type.Decimal.new(right))
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: Decimal do
|
||||
Decimal.compare(left, right)
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: Float do
|
||||
Decimal.compare(Ash.Type.Decimal.new(left), right)
|
||||
end
|
||||
|
||||
defcomparable left :: Decimal, right :: BitString do
|
||||
Decimal.compare(left, Ash.Type.Decimal.new(right))
|
||||
end
|
||||
|
|
|
@ -51,3 +51,9 @@ defmodule Ash.Type.NaiveDatetime do
|
|||
Ecto.Type.dump(:naive_datetime, value)
|
||||
end
|
||||
end
|
||||
|
||||
import Comp
|
||||
|
||||
defcomparable left :: NaiveDateTime, right :: NaiveDateTime do
|
||||
NaiveDateTime.compare(left, right)
|
||||
end
|
||||
|
|
|
@ -47,3 +47,9 @@ defmodule Ash.Type.Time do
|
|||
Ecto.Type.dump(:time, value)
|
||||
end
|
||||
end
|
||||
|
||||
import Ash.Type.Comparable
|
||||
|
||||
defcomparable left :: Time, right :: Time do
|
||||
Time.compare(left, right)
|
||||
end
|
||||
|
|
344
lib/comparable/comp.ex
Normal file
344
lib/comparable/comp.ex
Normal file
|
@ -0,0 +1,344 @@
|
|||
unless Code.ensure_loaded?(Comp) do
|
||||
defmodule Comp do
|
||||
@moduledoc """
|
||||
Provides utilities to implement and work with `Comparable` types
|
||||
"""
|
||||
|
||||
@type left :: term
|
||||
@type right :: term
|
||||
|
||||
defmacro gt, do: :gt
|
||||
defmacro lt, do: :lt
|
||||
defmacro eq, do: :eq
|
||||
|
||||
@doc """
|
||||
Helper to define ordering relation for pair of types,
|
||||
accepts two `term :: type` pairs
|
||||
and block of code where relation is described.
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> quote do
|
||||
...> use Comp
|
||||
...> defmodule Foo do
|
||||
...> defstruct [:value, :meta]
|
||||
...> end
|
||||
...> defmodule Bar do
|
||||
...> defstruct [:value, :meta]
|
||||
...> end
|
||||
...> defcomparable %Foo{value: left} :: Foo, %Foo{value: right} :: Foo do
|
||||
...> Comp.compare(left, right)
|
||||
...> end
|
||||
...> defcomparable %Foo{value: left} :: Foo, %Bar{value: right} :: Bar do
|
||||
...> Comp.compare(left, right)
|
||||
...> end
|
||||
...> defcomparable %Foo{value: left} :: Foo, right :: Integer do
|
||||
...> Comp.compare(left, right)
|
||||
...> end
|
||||
...> end
|
||||
...> |> Code.compile_quoted
|
||||
iex> quote do
|
||||
...> x = %Foo{value: 1, meta: 1}
|
||||
...> y = %Foo{value: 1, meta: 2}
|
||||
...> Comp.equal?(x, y) && Comp.equal?(y, x)
|
||||
...> end
|
||||
...> |> Code.eval_quoted
|
||||
...> |> elem(0)
|
||||
true
|
||||
iex> quote do
|
||||
...> x = %Foo{value: 1, meta: 1}
|
||||
...> y = %Bar{value: 1, meta: 2}
|
||||
...> Comp.equal?(x, y) && Comp.equal?(y, x)
|
||||
...> end
|
||||
...> |> Code.eval_quoted
|
||||
...> |> elem(0)
|
||||
true
|
||||
iex> quote do
|
||||
...> x = %Foo{value: 1, meta: 1}
|
||||
...> y = 1
|
||||
...> Comp.equal?(x, y) && Comp.equal?(y, x)
|
||||
...> end
|
||||
...> |> Code.eval_quoted
|
||||
...> |> elem(0)
|
||||
true
|
||||
```
|
||||
"""
|
||||
defmacro defcomparable(
|
||||
{:"::", _, [left_expression, quoted_left_type]},
|
||||
{:"::", _, [right_expression, quoted_right_type]},
|
||||
do: code
|
||||
) do
|
||||
{left_type, []} = Code.eval_quoted(quoted_left_type, [], __CALLER__)
|
||||
|
||||
{right_type, []} = Code.eval_quoted(quoted_right_type, [], __CALLER__)
|
||||
|
||||
lr_type =
|
||||
[Comparable, Type, left_type, To, right_type]
|
||||
|> Module.concat()
|
||||
|
||||
rl_type =
|
||||
[Comparable, Type, right_type, To, left_type]
|
||||
|> Module.concat()
|
||||
|
||||
lr_impl =
|
||||
quote do
|
||||
defmodule unquote(lr_type) do
|
||||
@fields [:left, :right]
|
||||
@enforce_keys @fields
|
||||
defstruct @fields
|
||||
end
|
||||
|
||||
defimpl Comparable, for: unquote(lr_type) do
|
||||
def compare(%unquote(lr_type){
|
||||
left: unquote(left_expression),
|
||||
right: unquote(right_expression)
|
||||
}) do
|
||||
unquote(code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if lr_type == rl_type do
|
||||
lr_impl
|
||||
else
|
||||
quote do
|
||||
unquote(lr_impl)
|
||||
|
||||
defmodule unquote(rl_type) do
|
||||
@fields [:left, :right]
|
||||
@enforce_keys @fields
|
||||
defstruct @fields
|
||||
end
|
||||
|
||||
defimpl Comparable, for: unquote(rl_type) do
|
||||
def compare(%unquote(rl_type){
|
||||
left: unquote(right_expression),
|
||||
right: unquote(left_expression)
|
||||
}) do
|
||||
unquote(code)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Is left term equal to right term?
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.equal?(1, 1)
|
||||
true
|
||||
iex> Comp.equal?(1, :hello)
|
||||
false
|
||||
```
|
||||
"""
|
||||
@spec equal?(left, right) :: boolean
|
||||
def equal?(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare() == eq()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Is left term not equal to right term?
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.not_equal?(1, 1)
|
||||
false
|
||||
iex> Comp.not_equal?(1, :hello)
|
||||
true
|
||||
```
|
||||
"""
|
||||
@spec not_equal?(left, right) :: boolean
|
||||
def not_equal?(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare() != eq()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Is left term greater than right term?
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.greater_than?(1, 1)
|
||||
false
|
||||
iex> Comp.greater_than?(1, 2)
|
||||
false
|
||||
iex> Comp.greater_than?(2, 1)
|
||||
true
|
||||
"""
|
||||
@spec greater_than?(left, right) :: boolean
|
||||
def greater_than?(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare() == gt()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Is left term less than right term?
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.less_than?(1, 1)
|
||||
false
|
||||
iex> Comp.less_than?(1, 2)
|
||||
true
|
||||
iex> Comp.less_than?(2, 1)
|
||||
false
|
||||
"""
|
||||
@spec less_than?(left, right) :: boolean
|
||||
def less_than?(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare() == lt()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Is left term greater or equal to right term?
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.greater_or_equal?(1, 1)
|
||||
true
|
||||
iex> Comp.greater_or_equal?(1, 2)
|
||||
false
|
||||
iex> Comp.greater_or_equal?(2, 1)
|
||||
true
|
||||
"""
|
||||
@spec greater_or_equal?(left, right) :: boolean
|
||||
def greater_or_equal?(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare() != lt()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Is left term less or equal to right term?
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.less_or_equal?(1, 1)
|
||||
true
|
||||
iex> Comp.less_or_equal?(1, 2)
|
||||
true
|
||||
iex> Comp.less_or_equal?(2, 1)
|
||||
false
|
||||
"""
|
||||
@spec less_or_equal?(left, right) :: boolean
|
||||
def less_or_equal?(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare() != gt()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the biggest of the two given terms, if terms are equal - then the first one is returned
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.max(1, 1)
|
||||
1
|
||||
iex> Comp.max(1, 2)
|
||||
2
|
||||
iex> Comp.max(2, 1)
|
||||
2
|
||||
```
|
||||
"""
|
||||
@spec max(left, right) :: left | right
|
||||
def max(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare()
|
||||
|> case do
|
||||
gt() -> left
|
||||
lt() -> right
|
||||
eq() -> left
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the smallest of the two given terms, if terms are equal - then the first one is returned
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.min(1, 1)
|
||||
1
|
||||
iex> Comp.min(1, 2)
|
||||
1
|
||||
iex> Comp.min(2, 1)
|
||||
1
|
||||
```
|
||||
"""
|
||||
@spec min(left, right) :: left | right
|
||||
def min(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare()
|
||||
|> case do
|
||||
gt() -> right
|
||||
lt() -> left
|
||||
eq() -> left
|
||||
end
|
||||
end
|
||||
|
||||
def type_of(v) when is_atom(v), do: Atom
|
||||
def type_of(v) when is_bitstring(v), do: BitString
|
||||
def type_of(v) when is_float(v), do: Float
|
||||
def type_of(v) when is_function(v), do: Function
|
||||
def type_of(v) when is_integer(v), do: Integer
|
||||
def type_of(v) when is_pid(v), do: PID
|
||||
def type_of(v) when is_port(v), do: Port
|
||||
def type_of(v) when is_reference(v), do: Reference
|
||||
def type_of(v) when is_tuple(v), do: Tuple
|
||||
def type_of(v) when is_list(v), do: List
|
||||
def type_of(%t{}), do: t
|
||||
def type_of(v) when is_map(v), do: Map
|
||||
|
||||
@doc """
|
||||
Compare left and right term
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
iex> Comp.compare(1, 2)
|
||||
:lt
|
||||
iex> Comp.compare(2, 1)
|
||||
:gt
|
||||
iex> Comp.compare(1, 1)
|
||||
:eq
|
||||
```
|
||||
"""
|
||||
@spec compare(left, right) :: Comparable.ord()
|
||||
def compare(left, right) do
|
||||
left
|
||||
|> new(right)
|
||||
|> Comparable.compare()
|
||||
end
|
||||
|
||||
defp new(left, right) do
|
||||
lr_type =
|
||||
try do
|
||||
[Comparable, Type, type_of(left), To, type_of(right)]
|
||||
|> Module.safe_concat()
|
||||
rescue
|
||||
ArgumentError ->
|
||||
[Comparable, Type, Any, To, Any]
|
||||
|> Module.safe_concat()
|
||||
end
|
||||
|
||||
%{__struct__: lr_type, left: left, right: right}
|
||||
end
|
||||
end
|
||||
end
|
16
lib/comparable/comparable.ex
Normal file
16
lib/comparable/comparable.ex
Normal file
|
@ -0,0 +1,16 @@
|
|||
unless Code.ensure_loaded?(Comparable) do
|
||||
defprotocol Comparable do
|
||||
@moduledoc """
|
||||
Protocol which describes ordering relation for pair of types
|
||||
"""
|
||||
|
||||
@type t :: Comparable.t()
|
||||
@type ord :: :gt | :lt | :eq
|
||||
|
||||
@doc """
|
||||
Accepts struct with fields :left and :right and returns ord value
|
||||
"""
|
||||
@spec compare(t) :: ord
|
||||
def compare(left_and_right)
|
||||
end
|
||||
end
|
91
lib/comparable/defaults.ex
Normal file
91
lib/comparable/defaults.ex
Normal file
|
@ -0,0 +1,91 @@
|
|||
import Comp
|
||||
|
||||
defcomparable left :: Any, right :: Any do
|
||||
case {left, right} do
|
||||
{_, _} when left == right ->
|
||||
Comp.eq()
|
||||
|
||||
{%name{}, %name{}} ->
|
||||
left
|
||||
|> Map.from_struct()
|
||||
|> Comp.compare(Map.from_struct(right))
|
||||
|
||||
{_, _} when left > right ->
|
||||
Comp.gt()
|
||||
|
||||
{_, _} when left < right ->
|
||||
Comp.lt()
|
||||
end
|
||||
end
|
||||
|
||||
defcomparable left :: List, right :: List do
|
||||
left
|
||||
|> Stream.zip(right)
|
||||
|> Enum.reduce_while(Comp.eq(), fn {lx, rx}, Comp.eq() ->
|
||||
lx
|
||||
|> Comp.compare(rx)
|
||||
|> case do
|
||||
res when res in [Comp.gt(), Comp.lt()] -> {:halt, res}
|
||||
Comp.eq() = res -> {:cont, res}
|
||||
end
|
||||
end)
|
||||
|> case do
|
||||
res when res in [Comp.gt(), Comp.lt()] ->
|
||||
res
|
||||
|
||||
Comp.eq() ->
|
||||
left_length = length(left)
|
||||
right_length = length(right)
|
||||
|
||||
cond do
|
||||
left_length > right_length -> Comp.gt()
|
||||
left_length < right_length -> Comp.lt()
|
||||
true -> Comp.eq()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defcomparable left :: Map, right :: Map do
|
||||
left_length = map_size(left)
|
||||
right_length = map_size(right)
|
||||
|
||||
cond do
|
||||
left_length > right_length ->
|
||||
Comp.gt()
|
||||
|
||||
left_length < right_length ->
|
||||
Comp.lt()
|
||||
|
||||
true ->
|
||||
left
|
||||
|> Map.keys()
|
||||
|> Comp.compare(right |> Map.keys())
|
||||
|> case do
|
||||
res when res in [Comp.gt(), Comp.lt()] ->
|
||||
res
|
||||
|
||||
Comp.eq() ->
|
||||
left
|
||||
|> Map.values()
|
||||
|> Comp.compare(right |> Map.values())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defcomparable left :: Tuple, right :: Tuple do
|
||||
left_length = tuple_size(left)
|
||||
right_length = tuple_size(right)
|
||||
|
||||
cond do
|
||||
left_length > right_length ->
|
||||
Comp.gt()
|
||||
|
||||
left_length < right_length ->
|
||||
Comp.lt()
|
||||
|
||||
true ->
|
||||
left
|
||||
|> Tuple.to_list()
|
||||
|> Comp.compare(right |> Tuple.to_list())
|
||||
end
|
||||
end
|
1
mix.exs
1
mix.exs
|
@ -343,7 +343,6 @@ defmodule Ash.MixProject do
|
|||
{:ets, "~> 0.8"},
|
||||
# Data & types
|
||||
{:decimal, "~> 2.0"},
|
||||
{:comparable, "~> 1.0"},
|
||||
{:jason, ">= 1.0.0"},
|
||||
# Observability
|
||||
{:telemetry, "~> 1.1"},
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -1,7 +1,6 @@
|
|||
%{
|
||||
"benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"},
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
||||
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||
|
@ -48,7 +47,6 @@
|
|||
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
|
||||
"stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||
"ucwidth": {:hex, :ucwidth, "0.2.0", "1f0a440f541d895dff142275b96355f7e91e15bca525d4a0cc788ea51f0e3441", [:mix], [], "hexpm", "c1efd1798b8eeb11fb2bec3cafa3dd9c0c3647bee020543f0340b996177355bf"},
|
||||
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
|
||||
|
|
Loading…
Reference in a new issue