mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 13:33:20 +12:00
da91e2891e
* Pass options without :templated tuple to after_batch * fix: properly iterate non templated opts --------- Co-authored-by: Jonatan Männchen <jonatan@maennchen.ch>
328 lines
7.8 KiB
Elixir
328 lines
7.8 KiB
Elixir
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
|