diff --git a/lib/eq.ex b/lib/eq.ex index 8b91363a..2031c928 100644 --- a/lib/eq.ex +++ b/lib/eq.ex @@ -9,8 +9,8 @@ defmodule Funx.Eq do 1. **Utility functions** for working with equality comparisons: - `contramap/2` - Transform equality checks via projections - `eq?/3`, `not_eq?/3` - Direct equality checks - - `append_all/2`, `append_any/2` - Combine comparators - - `concat_all/1`, `concat_any/1` - Combine lists of comparators + - `compose_all/2`, `compose_any/2` - Combine comparators + - `compose_all/1`, `compose_any/1` - Combine lists of comparators - `to_predicate/2` - Convert to single-argument predicates 2. **Declarative DSL** for building complex equality comparators: @@ -375,7 +375,7 @@ defmodule Funx.Eq do end @doc """ - Combines two equality comparators using the `Eq.All` monoid. + Composes two equality comparators using the `Eq.All` monoid. This function merges two equality comparisons, requiring **both** to return `true` for the final result to be considered equal. This enforces a **strict** equality rule, @@ -385,61 +385,61 @@ defmodule Funx.Eq do iex> eq1 = Funx.Eq.contramap(& &1.name) iex> eq2 = Funx.Eq.contramap(& &1.age) - iex> combined = Funx.Eq.append_all(eq1, eq2) + iex> combined = Funx.Eq.compose_all(eq1, eq2) iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined) true iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) false """ - @spec append_all(eq_t(), eq_t()) :: eq_t() - def append_all(a, b) do + @spec compose_all(eq_t(), eq_t()) :: eq_t() + def compose_all(a, b) do m_append(%Monoid.Eq.All{}, a, b) end @doc """ - Combines two equality comparators using the `Eq.Any` monoid. + Composes a list of equality comparators using the `Eq.All` monoid. - This function merges two equality comparisons, where **at least one** - must return `true` for the final result to be considered equal. + The resulting comparator requires **all** comparators in the list to agree + that two values are equal. ## Examples iex> eq1 = Funx.Eq.contramap(& &1.name) iex> eq2 = Funx.Eq.contramap(& &1.age) - iex> combined = Funx.Eq.append_any(eq1, eq2) - iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) + iex> combined = Funx.Eq.compose_all([eq1, eq2]) + iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined) true - iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined) + iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) false """ - @spec append_any(eq_t(), eq_t()) :: eq_t() - def append_any(a, b) do - m_append(%Monoid.Eq.Any{}, a, b) + @spec compose_all([eq_t()]) :: eq_t() + def compose_all(eq_list) when is_list(eq_list) do + m_concat(%Monoid.Eq.All{}, eq_list) end @doc """ - Concatenates a list of equality comparators using the `Eq.All` monoid. + Composes two equality comparators using the `Eq.Any` monoid. - The resulting comparator requires **all** comparators in the list to agree - that two values are equal. + This function merges two equality comparisons, where **at least one** + must return `true` for the final result to be considered equal. ## Examples iex> eq1 = Funx.Eq.contramap(& &1.name) iex> eq2 = Funx.Eq.contramap(& &1.age) - iex> combined = Funx.Eq.concat_all([eq1, eq2]) - iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined) - true + iex> combined = Funx.Eq.compose_any(eq1, eq2) iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) + true + iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined) false """ - @spec concat_all([eq_t()]) :: eq_t() - def concat_all(eq_list) when is_list(eq_list) do - m_concat(%Monoid.Eq.All{}, eq_list) + @spec compose_any(eq_t(), eq_t()) :: eq_t() + def compose_any(a, b) do + m_append(%Monoid.Eq.Any{}, a, b) end @doc """ - Concatenates a list of equality comparators using the `Eq.Any` monoid. + Composes a list of equality comparators using the `Eq.Any` monoid. The resulting comparator allows **any** comparator in the list to determine equality, making it more permissive. @@ -448,17 +448,33 @@ defmodule Funx.Eq do iex> eq1 = Funx.Eq.contramap(& &1.name) iex> eq2 = Funx.Eq.contramap(& &1.age) - iex> combined = Funx.Eq.concat_any([eq1, eq2]) + iex> combined = Funx.Eq.compose_any([eq1, eq2]) iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) true iex> Funx.Eq.eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined) false """ - @spec concat_any([eq_t()]) :: eq_t() - def concat_any(eq_list) when is_list(eq_list) do + @spec compose_any([eq_t()]) :: eq_t() + def compose_any(eq_list) when is_list(eq_list) do m_concat(%Monoid.Eq.Any{}, eq_list) end + @deprecated "Use compose_all/2 instead" + @spec append_all(eq_t(), eq_t()) :: eq_t() + def append_all(a, b), do: compose_all(a, b) + + @deprecated "Use compose_any/2 instead" + @spec append_any(eq_t(), eq_t()) :: eq_t() + def append_any(a, b), do: compose_any(a, b) + + @deprecated "Use compose_all/1 instead" + @spec concat_all([eq_t()]) :: eq_t() + def concat_all(eq_list) when is_list(eq_list), do: compose_all(eq_list) + + @deprecated "Use compose_any/1 instead" + @spec concat_any([eq_t()]) :: eq_t() + def concat_any(eq_list) when is_list(eq_list), do: compose_any(eq_list) + @doc """ Converts an `Eq` comparator into a single-argument predicate function for use in `Enum` functions. diff --git a/lib/eq/dsl/block.ex b/lib/eq/dsl/block.ex index 1f200e0a..c7682abb 100644 --- a/lib/eq/dsl/block.ex +++ b/lib/eq/dsl/block.ex @@ -5,8 +5,8 @@ defmodule Funx.Eq.Dsl.Block do # ## Purpose # # Blocks group multiple equality checks with a composition strategy: - # - `:all` → All children must pass (AND logic) via concat_all - # - `:any` → At least one child must pass (OR logic) via concat_any + # - `:all` → All children must pass (AND logic) via compose_all + # - `:any` → At least one child must pass (OR logic) via compose_any # # ## Structure # diff --git a/lib/eq/dsl/executor.ex b/lib/eq/dsl/executor.ex index 68504ff4..e2878b06 100644 --- a/lib/eq/dsl/executor.ex +++ b/lib/eq/dsl/executor.ex @@ -24,10 +24,10 @@ defmodule Funx.Eq.Dsl.Executor do # # The executor recursively walks the node tree: # - Step nodes → Generate contramap/to_eq_map calls - # - Block nodes → Generate concat_all/concat_any calls + # - Block nodes → Generate compose_all/compose_any calls # - Negate flag → Swap eq?/not_eq? functions # - # Top-level nodes are implicitly combined with concat_all (AND logic). + # Top-level nodes are implicitly combined with compose_all (AND logic). alias Funx.Eq alias Funx.Eq.Dsl.{Block, Step} @@ -43,10 +43,10 @@ defmodule Funx.Eq.Dsl.Executor do Each node is converted to: - Step (on) → `contramap(projection, eq)` - Step (not_on) → `contramap(projection, negate(eq))` - - Block (all) → `concat_all([children...])` - - Block (any) → `concat_any([children...])` + - Block (all) → `compose_all([children...])` + - Block (any) → `compose_any([children...])` - Top-level nodes are combined with `concat_all` (implicit all strategy). + Top-level nodes are combined with `compose_all` (implicit all strategy). """ @spec execute_nodes(list(Step.t() | Block.t())) :: Macro.t() def execute_nodes([]) do @@ -60,7 +60,7 @@ defmodule Funx.Eq.Dsl.Executor do eq_asts = Enum.map(nodes, &node_to_ast/1) quote do - Eq.concat_all([unquote_splicing(eq_asts)]) + Eq.compose_all([unquote_splicing(eq_asts)]) end end @@ -68,7 +68,7 @@ defmodule Funx.Eq.Dsl.Executor do eq_asts = Enum.map(nodes, &node_to_ast/1) quote do - Eq.concat_any([unquote_splicing(eq_asts)]) + Eq.compose_any([unquote_splicing(eq_asts)]) end end diff --git a/lib/ord.ex b/lib/ord.ex index 9f00fe96..d4ad36ac 100644 --- a/lib/ord.ex +++ b/lib/ord.ex @@ -20,7 +20,7 @@ defmodule Funx.Ord do - `reverse/1` - Reverse ordering logic - `comparator/1` - Convert to Elixir comparator for `Enum.sort/2` - `to_eq/1` - Convert to equality comparator - - `append/2`, `concat/1` - Combine multiple orderings + - `compose/2`, `compose/1` - Combine multiple orderings ## DSL @@ -392,7 +392,7 @@ defmodule Funx.Ord do end @doc """ - Appends two `Ord` instances, combining their comparison logic. + Composes two `Ord` instances, combining their comparison logic. If the first `Ord` comparator determines an order, that result is used. If not, the second comparator is used as a fallback. @@ -401,17 +401,17 @@ defmodule Funx.Ord do iex> ord1 = Funx.Ord.contramap(& &1.age, Funx.Ord.Protocol.Any) iex> ord2 = Funx.Ord.contramap(& &1.name, Funx.Ord.Protocol.Any) - iex> combined = Funx.Ord.append(ord1, ord2) + iex> combined = Funx.Ord.compose(ord1, ord2) iex> combined.lt?.(%{age: 30, name: "Alice"}, %{age: 30, name: "Bob"}) true """ - @spec append(ord_t(), ord_t()) :: ord_t() - def append(a, b) do + @spec compose(ord_t(), ord_t()) :: ord_t() + def compose(a, b) do m_append(%Funx.Monoid.Ord{}, a, b) end @doc """ - Concatenates a list of `Ord` instances into a single composite comparator. + Composes a list of `Ord` instances into a single composite comparator. This function reduces a list of `Ord` comparators into a single `Ord`, applying them in sequence until an order is determined. @@ -422,15 +422,23 @@ defmodule Funx.Ord do ...> Funx.Ord.contramap(& &1.age, Funx.Ord.Protocol.Any), ...> Funx.Ord.contramap(& &1.name, Funx.Ord.Protocol.Any) ...> ] - iex> combined = Funx.Ord.concat(ord_list) + iex> combined = Funx.Ord.compose(ord_list) iex> combined.gt?.(%{age: 25, name: "Charlie"}, %{age: 25, name: "Bob"}) true """ - @spec concat([ord_t()]) :: ord_t() - def concat(ord_list) when is_list(ord_list) do + @spec compose([ord_t()]) :: ord_t() + def compose(ord_list) when is_list(ord_list) do m_concat(%Funx.Monoid.Ord{}, ord_list) end + @deprecated "Use compose/2 instead" + @spec append(ord_t(), ord_t()) :: ord_t() + def append(a, b), do: compose(a, b) + + @deprecated "Use compose/1 instead" + @spec concat([ord_t()]) :: ord_t() + def concat(ord_list) when is_list(ord_list), do: compose(ord_list) + def to_ord_map(%{lt?: lt_fun, le?: le_fun, gt?: gt_fun, ge?: ge_fun} = ord_map) when is_function(lt_fun, 2) and is_function(le_fun, 2) and diff --git a/lib/ord/dsl/executor.ex b/lib/ord/dsl/executor.ex index 2f132d70..baea260f 100644 --- a/lib/ord/dsl/executor.ex +++ b/lib/ord/dsl/executor.ex @@ -32,7 +32,7 @@ defmodule Funx.Ord.Dsl.Executor do - `:asc` → `contramap(projection, ord)` - `:desc` → `reverse(contramap(projection, ord))` - Multiple steps are combined with `concat([...])` (monoid append). + Multiple steps are combined with `compose([...])` (monoid append). If two values are equal on all specified fields, they compare as equal. Users can add an explicit tiebreaker if needed (e.g., `asc &Function.identity/1`). @@ -50,7 +50,7 @@ defmodule Funx.Ord.Dsl.Executor do ord_asts = Enum.map(steps, &step_to_ord_ast/1) quote do - Ord.concat([unquote_splicing(ord_asts)]) + Ord.compose([unquote_splicing(ord_asts)]) end end @@ -166,7 +166,7 @@ defmodule Funx.Ord.Dsl.Executor do - ord do ... end - Ord.contramap(...) - Ord.reverse(...) - - Ord.concat([...]) + - Ord.compose([...]) """ end end diff --git a/livebooks/eq/eq.livemd b/livebooks/eq/eq.livemd index 4edf975a..13d90a91 100644 --- a/livebooks/eq/eq.livemd +++ b/livebooks/eq/eq.livemd @@ -128,9 +128,9 @@ eq_by?(& &1.age, %{age: 30}, %{age: 25}) ## Monoid Operations -### append_all/2 +### compose_all/2 -Combines two equality comparators using the `Eq.All` monoid. +Composes two equality comparators using the `Eq.All` monoid. This function merges two equality comparisons, requiring **both** to return `true` for the final result to be considered equal. This enforces a **strict** equality rule, @@ -139,20 +139,20 @@ where all comparators must agree. ```elixir eq1 = contramap(& &1.name) eq2 = contramap(& &1.age) -combined = append_all(eq1, eq2) +combined = compose_all(eq1, eq2) eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined) ``` ```elixir eq1 = contramap(& &1.name) eq2 = contramap(& &1.age) -combined = append_all(eq1, eq2) +combined = compose_all(eq1, eq2) eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) ``` -### append_any/2 +### compose_any/2 -Combines two equality comparators using the `Eq.Any` monoid. +Composes two equality comparators using the `Eq.Any` monoid. This function merges two equality comparisons, where **at least one** must return `true` for the final result to be considered equal. @@ -160,20 +160,20 @@ must return `true` for the final result to be considered equal. ```elixir eq1 = contramap(& &1.name) eq2 = contramap(& &1.age) -combined = append_any(eq1, eq2) +combined = compose_any(eq1, eq2) eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) ``` ```elixir eq1 = contramap(& &1.name) eq2 = contramap(& &1.age) -combined = append_any(eq1, eq2) +combined = compose_any(eq1, eq2) eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined) ``` -### concat_all/1 +### compose_all/1 -Concatenates a list of equality comparators using the `Eq.All` monoid. +Composes a list of equality comparators using the `Eq.All` monoid. The resulting comparator requires **all** comparators in the list to agree that two values are equal. @@ -181,20 +181,20 @@ that two values are equal. ```elixir eq1 = contramap(& &1.name) eq2 = contramap(& &1.age) -combined = concat_all([eq1, eq2]) +combined = compose_all([eq1, eq2]) eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 30}, combined) ``` ```elixir eq1 = contramap(& &1.name) eq2 = contramap(& &1.age) -combined = concat_all([eq1, eq2]) +combined = compose_all([eq1, eq2]) eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) ``` -### concat_any/1 +### compose_any/1 -Concatenates a list of equality comparators using the `Eq.Any` monoid. +Composes a list of equality comparators using the `Eq.Any` monoid. The resulting comparator allows **any** comparator in the list to determine equality, making it more permissive. @@ -202,14 +202,14 @@ equality, making it more permissive. ```elixir eq1 = contramap(& &1.name) eq2 = contramap(& &1.age) -combined = concat_any([eq1, eq2]) +combined = compose_any([eq1, eq2]) eq?(%{name: "Alice", age: 30}, %{name: "Alice", age: 25}, combined) ``` ```elixir eq1 = contramap(& &1.name) eq2 = contramap(& &1.age) -combined = concat_any([eq1, eq2]) +combined = compose_any([eq1, eq2]) eq?(%{name: "Alice", age: 30}, %{name: "Bob", age: 25}, combined) ``` diff --git a/livebooks/ord/ord.livemd b/livebooks/ord/ord.livemd index a3a0ebba..b1fd6d7a 100644 --- a/livebooks/ord/ord.livemd +++ b/livebooks/ord/ord.livemd @@ -210,9 +210,9 @@ eq = to_eq(Funx.Ord.Protocol) eq.eq?.(5, 5) ``` -### append/2 +### compose/2 -Appends two `Ord` instances, combining their comparison logic. +Composes two `Ord` instances, combining their comparison logic. If the first `Ord` comparator determines an order, that result is used. If not, the second comparator is used as a fallback. @@ -220,13 +220,13 @@ If not, the second comparator is used as a fallback. ```elixir ord1 = contramap(& &1.age, Funx.Ord.Protocol) ord2 = contramap(& &1.name, Funx.Ord.Protocol) -combined = append(ord1, ord2) +combined = compose(ord1, ord2) combined.lt?.(%{age: 30, name: "Alice"}, %{age: 30, name: "Bob"}) ``` -### concat/1 +### compose/1 -Concatenates a list of `Ord` instances into a single composite comparator. +Composes a list of `Ord` instances into a single composite comparator. This function reduces a list of `Ord` comparators into a single `Ord`, applying them in sequence until an order is determined. @@ -236,6 +236,6 @@ ord_list = [ contramap(& &1.age, Funx.Ord.Protocol), contramap(& &1.name, Funx.Ord.Protocol) ] -combined = concat(ord_list) +combined = compose(ord_list) combined.gt?.(%{age: 25, name: "Charlie"}, %{age: 25, name: "Bob"}) ``` diff --git a/test/eq_test.exs b/test/eq_test.exs index 7dddaee3..399fb49e 100644 --- a/test/eq_test.exs +++ b/test/eq_test.exs @@ -398,100 +398,143 @@ defmodule Funx.EqTest do defp eq_name, do: Funx.Eq.contramap(& &1.name) defp eq_age, do: Funx.Eq.contramap(& &1.age) - defp eq_all, do: Funx.Eq.append_all(eq_name(), eq_age()) - defp eq_any, do: Funx.Eq.append_any(eq_name(), eq_age()) - defp eq_concat_all, do: Funx.Eq.concat_all([eq_name(), eq_age()]) - defp eq_concat_any, do: Funx.Eq.concat_any([eq_name(), eq_age()]) + defp eq_compose_all_2, do: Funx.Eq.compose_all(eq_name(), eq_age()) + defp eq_compose_any_2, do: Funx.Eq.compose_any(eq_name(), eq_age()) - defp eq_concat_all_default, do: Funx.Eq.concat_all([Funx.Eq.Protocol]) - defp eq_concat_any_default, do: Funx.Eq.concat_any([Funx.Eq.Protocol]) + defp eq_compose_all_list, do: Funx.Eq.compose_all([eq_name(), eq_age()]) + defp eq_compose_any_list, do: Funx.Eq.compose_any([eq_name(), eq_age()]) - describe "Eq Monoid - append" do - test "append with equal persons" do + defp eq_compose_all_default, do: Funx.Eq.compose_all([Funx.Eq.Protocol]) + defp eq_compose_any_default, do: Funx.Eq.compose_any([Funx.Eq.Protocol]) + + describe "Eq Monoid - deprecated append/concat" do + test "append_all/2 delegates to compose_all/2" do alice1 = %Person{name: "Alice", age: 30} alice2 = %Person{name: "Alice", age: 30} - assert Funx.Eq.eq?(alice1, alice2, eq_name()) - assert Funx.Eq.eq?(alice1, alice2, eq_age()) - assert Funx.Eq.eq?(alice1, alice2, eq_all()) - assert Funx.Eq.eq?(alice1, alice2, eq_any()) - - refute Funx.Eq.not_eq?(alice1, alice2, eq_name()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_age()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_all()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_any()) + combined = Funx.Eq.append_all(eq_name(), eq_age()) + assert Funx.Eq.eq?(alice1, alice2, combined) end - test "append with not equal persons" do + test "append_any/2 delegates to compose_any/2" do alice1 = %Person{name: "Alice", age: 30} alice2 = %Person{name: "Alice", age: 29} - assert Funx.Eq.eq?(alice1, alice2, eq_name()) - refute Funx.Eq.eq?(alice1, alice2, eq_age()) - refute Funx.Eq.eq?(alice1, alice2, eq_all()) - assert Funx.Eq.eq?(alice1, alice2, eq_any()) + combined = Funx.Eq.append_any(eq_name(), eq_age()) + assert Funx.Eq.eq?(alice1, alice2, combined) + end - refute Funx.Eq.not_eq?(alice1, alice2, eq_name()) - assert Funx.Eq.not_eq?(alice1, alice2, eq_age()) - assert Funx.Eq.not_eq?(alice1, alice2, eq_all()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_any()) + test "concat_all/1 delegates to compose_all/1" do + alice1 = %Person{name: "Alice", age: 30} + alice2 = %Person{name: "Alice", age: 30} + + combined = Funx.Eq.concat_all([eq_name(), eq_age()]) + assert Funx.Eq.eq?(alice1, alice2, combined) + end + + test "concat_any/1 delegates to compose_any/1" do + alice1 = %Person{name: "Alice", age: 30} + alice2 = %Person{name: "Alice", age: 29} + + combined = Funx.Eq.concat_any([eq_name(), eq_age()]) + assert Funx.Eq.eq?(alice1, alice2, combined) end end - describe "Eq Monoid - concat" do - test "concat with equal persons" do + describe "Eq Monoid - compose_all" do + test "compose_all/2 combines with AND logic" do alice1 = %Person{name: "Alice", age: 30} alice2 = %Person{name: "Alice", age: 30} + alice3 = %Person{name: "Alice", age: 29} - assert Funx.Eq.eq?(alice1, alice2, eq_name()) - assert Funx.Eq.eq?(alice1, alice2, eq_age()) - assert Funx.Eq.eq?(alice1, alice2, eq_concat_all()) - assert Funx.Eq.eq?(alice1, alice2, eq_concat_any()) + assert Funx.Eq.eq?(alice1, alice2, eq_compose_all_2()) + refute Funx.Eq.eq?(alice1, alice3, eq_compose_all_2()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_name()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_age()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_concat_all()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_concat_any()) + refute Funx.Eq.not_eq?(alice1, alice2, eq_compose_all_2()) + assert Funx.Eq.not_eq?(alice1, alice3, eq_compose_all_2()) end - test "concat with not equal persons" do + test "compose_all/1 with list combines with AND logic" do alice1 = %Person{name: "Alice", age: 30} - alice2 = %Person{name: "Alice", age: 29} + alice2 = %Person{name: "Alice", age: 30} + alice3 = %Person{name: "Alice", age: 29} - assert Funx.Eq.eq?(alice1, alice2, eq_name()) - refute Funx.Eq.eq?(alice1, alice2, eq_age()) - refute Funx.Eq.eq?(alice1, alice2, eq_concat_all()) - assert Funx.Eq.eq?(alice1, alice2, eq_concat_any()) + assert Funx.Eq.eq?(alice1, alice2, eq_compose_all_list()) + refute Funx.Eq.eq?(alice1, alice3, eq_compose_all_list()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_name()) - assert Funx.Eq.not_eq?(alice1, alice2, eq_age()) - assert Funx.Eq.not_eq?(alice1, alice2, eq_concat_all()) - refute Funx.Eq.not_eq?(alice1, alice2, eq_any()) + refute Funx.Eq.not_eq?(alice1, alice2, eq_compose_all_list()) + assert Funx.Eq.not_eq?(alice1, alice3, eq_compose_all_list()) end - test "concat all with default (name)" do + test "compose_all/1 with default (name)" do alice1 = %Person{name: "Alice", age: 30} alice2 = %Person{name: "Alice", age: 29} bob = %Person{name: "Bob", age: 30} - assert Funx.Eq.eq?(alice1, alice2, eq_concat_all_default()) - refute Funx.Eq.eq?(alice1, bob, eq_concat_all_default()) + assert Funx.Eq.eq?(alice1, alice2, eq_compose_all_default()) + refute Funx.Eq.eq?(alice1, bob, eq_compose_all_default()) + + refute Funx.Eq.not_eq?(alice1, alice2, eq_compose_all_default()) + assert Funx.Eq.not_eq?(alice1, bob, eq_compose_all_default()) + end + + test "compose_all/1 with empty list behaves as identity (vacuous truth)" do + alice = %Person{name: "Alice", age: 30} + bob = %Person{name: "Bob", age: 25} + + empty_eq = Funx.Eq.compose_all([]) - refute Funx.Eq.not_eq?(alice1, alice2, eq_concat_all_default()) - assert Funx.Eq.not_eq?(alice1, bob, eq_concat_all_default()) + assert empty_eq.eq?.(alice, alice) + assert empty_eq.eq?.(alice, bob) end + end - test "concat any with default (name)" do + describe "Eq Monoid - compose_any" do + test "compose_any/2 combines with OR logic" do + alice1 = %Person{name: "Alice", age: 30} + alice2 = %Person{name: "Alice", age: 29} + bob = %Person{name: "Bob", age: 25} + + assert Funx.Eq.eq?(alice1, alice2, eq_compose_any_2()) + refute Funx.Eq.eq?(alice1, bob, eq_compose_any_2()) + + refute Funx.Eq.not_eq?(alice1, alice2, eq_compose_any_2()) + assert Funx.Eq.not_eq?(alice1, bob, eq_compose_any_2()) + end + + test "compose_any/1 with list combines with OR logic" do + alice1 = %Person{name: "Alice", age: 30} + alice2 = %Person{name: "Alice", age: 29} + bob = %Person{name: "Bob", age: 25} + + assert Funx.Eq.eq?(alice1, alice2, eq_compose_any_list()) + refute Funx.Eq.eq?(alice1, bob, eq_compose_any_list()) + + refute Funx.Eq.not_eq?(alice1, alice2, eq_compose_any_list()) + assert Funx.Eq.not_eq?(alice1, bob, eq_compose_any_list()) + end + + test "compose_any/1 with default (name)" do alice1 = %Person{name: "Alice", age: 30} alice2 = %Person{name: "Alice", age: 29} bob = %Person{name: "Bob", age: 30} - assert Funx.Eq.eq?(alice1, alice2, eq_concat_any_default()) - refute Funx.Eq.eq?(alice1, bob, eq_concat_any_default()) + assert Funx.Eq.eq?(alice1, alice2, eq_compose_any_default()) + refute Funx.Eq.eq?(alice1, bob, eq_compose_any_default()) + + refute Funx.Eq.not_eq?(alice1, alice2, eq_compose_any_default()) + assert Funx.Eq.not_eq?(alice1, bob, eq_compose_any_default()) + end + + test "compose_any/1 with empty list makes nothing equal" do + alice = %Person{name: "Alice", age: 30} + bob = %Person{name: "Bob", age: 25} + + empty_eq = Funx.Eq.compose_any([]) - refute Funx.Eq.not_eq?(alice1, alice2, eq_concat_any_default()) - assert Funx.Eq.not_eq?(alice1, bob, eq_concat_any_default()) + refute empty_eq.eq?.(alice, alice) + refute empty_eq.eq?.(alice, bob) end end @@ -749,10 +792,10 @@ defmodule Funx.EqTest do end describe "property: monoid laws" do - property "concat_all: empty list behaves as identity" do + property "compose_all/1: empty list behaves as identity" do check all(value <- integer()) do - # Empty concat_all should compare everything as equal (identity) - empty_eq = Funx.Eq.concat_all([]) + # Empty compose_all should compare everything as equal (identity) + empty_eq = Funx.Eq.compose_all([]) # With empty list, everything equals everything (vacuous truth) assert empty_eq.eq?.(value, value) @@ -760,10 +803,10 @@ defmodule Funx.EqTest do end end - property "concat_any: empty list behaves as identity" do + property "compose_any/1: empty list behaves as identity" do check all(value <- integer()) do - # Empty concat_any should compare nothing as equal - empty_eq = Funx.Eq.concat_any([]) + # Empty compose_any should compare nothing as equal + empty_eq = Funx.Eq.compose_any([]) # With empty list, nothing equals anything (even itself) refute empty_eq.eq?.(value, value) @@ -771,7 +814,7 @@ defmodule Funx.EqTest do end end - property "append_all combines equality checks with AND logic" do + property "compose_all/2 combines equality checks with AND logic" do check all( name1 <- string(:alphanumeric, min_length: 1), name2 <- string(:alphanumeric, min_length: 1), @@ -780,7 +823,7 @@ defmodule Funx.EqTest do ) do eq_name = Funx.Eq.contramap(& &1.name) eq_age = Funx.Eq.contramap(& &1.age) - eq_all = Funx.Eq.append_all(eq_name, eq_age) + eq_all = Funx.Eq.compose_all(eq_name, eq_age) person1 = %{name: name1, age: age1} person2 = %{name: name2, age: age2} @@ -794,7 +837,7 @@ defmodule Funx.EqTest do end end - property "append_any combines equality checks with OR logic" do + property "compose_any/2 combines equality checks with OR logic" do check all( name1 <- string(:alphanumeric, min_length: 1), name2 <- string(:alphanumeric, min_length: 1), @@ -803,7 +846,7 @@ defmodule Funx.EqTest do ) do eq_name = Funx.Eq.contramap(& &1.name) eq_age = Funx.Eq.contramap(& &1.age) - eq_any = Funx.Eq.append_any(eq_name, eq_age) + eq_any = Funx.Eq.compose_any(eq_name, eq_age) person1 = %{name: name1, age: age1} person2 = %{name: name2, age: age2} diff --git a/test/monoid/max_test.exs b/test/monoid/max_test.exs index 618de7c3..fa891b2b 100644 --- a/test/monoid/max_test.exs +++ b/test/monoid/max_test.exs @@ -15,7 +15,7 @@ defmodule Funx.Monoid.MaxTest do m_concat( %Monoid.Max{ value: Maybe.nothing(), - ord: concat([ord_age(), ord_ticket(), Funx.Ord.Protocol]) + ord: compose([ord_age(), ord_ticket(), Funx.Ord.Protocol]) }, people ) @@ -25,7 +25,7 @@ defmodule Funx.Monoid.MaxTest do m_concat( %Monoid.Max{ value: Maybe.nothing(), - ord: concat([Funx.Ord.Protocol, ord_age()]) + ord: compose([Funx.Ord.Protocol, ord_age()]) }, people ) @@ -35,7 +35,7 @@ defmodule Funx.Monoid.MaxTest do m_concat( %Monoid.Max{ value: Maybe.nothing(), - ord: concat([ord_ticket(), ord_age()]) + ord: compose([ord_ticket(), ord_age()]) }, people ) diff --git a/test/ord_test.exs b/test/ord_test.exs index f95b0fad..809b3f7b 100644 --- a/test/ord_test.exs +++ b/test/ord_test.exs @@ -22,11 +22,12 @@ defmodule Funx.OrdTest do defp ord_name, do: contramap(& &1.name) defp ord_age, do: contramap(& &1.age) defp ord_ticket, do: contramap(& &1.ticket) - defp ord_append, do: append(ord_name(), ord_age()) - defp ord_concat, do: concat([ord_name(), ord_age()]) - defp ord_concat_age, do: concat([ord_age(), ord_ticket(), Funx.Ord.Protocol]) - defp ord_concat_default, do: concat([Funx.Ord.Protocol]) - defp ord_empty, do: concat([]) + + defp ord_compose_2, do: compose(ord_name(), ord_age()) + defp ord_compose_list, do: compose([ord_name(), ord_age()]) + defp ord_compose_age, do: compose([ord_age(), ord_ticket(), Funx.Ord.Protocol]) + defp ord_compose_default, do: compose([Funx.Ord.Protocol]) + defp ord_compose_empty, do: compose([]) # ============================================================================ # Basic Operations Tests @@ -386,56 +387,72 @@ defmodule Funx.OrdTest do # Monoid Operations Tests # ============================================================================ - describe "Ord Monoid - append and concat" do - test "with ordered persons" do + describe "Ord Monoid - deprecated append/concat" do + test "append/2 delegates to compose/2" do + alice = %Person{name: "Alice", age: 30} + bob = %Person{name: "Bob", age: 25} + + combined = append(ord_name(), ord_age()) + assert compare(alice, bob, combined) == :lt + end + + test "concat/1 delegates to compose/1" do + alice = %Person{name: "Alice", age: 30} + bob = %Person{name: "Bob", age: 25} + + combined = concat([ord_name(), ord_age()]) + assert compare(alice, bob, combined) == :lt + end + end + + describe "Ord Monoid - compose" do + test "compose/2 combines two orderings" do alice = %Person{name: "Alice", age: 30, ticket: :b} bob = %Person{name: "Bob", age: 25, ticket: :a} - bob_b = %Person{name: "Bob", age: 30, ticket: :a} - bob_c = %Person{name: "Bob", age: 30, ticket: :b} - - assert compare(alice, bob, ord_name()) == :lt - assert compare(bob, alice, ord_name()) == :gt - assert compare(alice, alice, ord_name()) == :eq - assert compare(alice, bob, ord_age()) == :gt - assert compare(bob, alice, ord_age()) == :lt - assert compare(alice, alice, ord_age()) == :eq + assert compare(alice, bob, ord_compose_2()) == :lt + assert compare(bob, alice, ord_compose_2()) == :gt + assert compare(alice, alice, ord_compose_2()) == :eq + end - assert compare(alice, bob, ord_ticket()) == :gt - assert compare(bob, alice, ord_ticket()) == :lt - assert compare(alice, alice, ord_ticket()) == :eq + test "compose/1 with list combines orderings lexicographically" do + alice = %Person{name: "Alice", age: 30, ticket: :b} + bob = %Person{name: "Bob", age: 25, ticket: :a} + bob_b = %Person{name: "Bob", age: 30, ticket: :a} + bob_c = %Person{name: "Bob", age: 30, ticket: :b} - assert compare(alice, bob, ord_append()) == :lt - assert compare(bob, alice, ord_append()) == :gt - assert compare(alice, alice, ord_append()) == :eq + assert compare(alice, bob, ord_compose_list()) == :lt + assert compare(bob, alice, ord_compose_list()) == :gt + assert compare(alice, alice, ord_compose_list()) == :eq - assert compare(alice, bob, ord_concat()) == :lt - assert compare(bob, alice, ord_concat()) == :gt - assert compare(alice, alice, ord_concat()) == :eq + assert compare(alice, bob, ord_compose_age()) == :gt + assert compare(bob, alice, ord_compose_age()) == :lt + assert compare(alice, alice, ord_compose_age()) == :eq + assert compare(alice, bob_b, ord_compose_age()) == :gt + assert compare(alice, bob_c, ord_compose_age()) == :lt - assert compare(alice, bob, ord_concat_age()) == :gt - assert compare(bob, alice, ord_concat_age()) == :lt - assert compare(alice, alice, ord_concat_age()) == :eq - assert compare(alice, bob_b, ord_concat_age()) == :gt - assert compare(alice, bob_c, ord_concat_age()) == :lt + assert ord_compose_list().lt?.(alice, bob) + assert ord_compose_list().le?.(alice, alice) + assert ord_compose_list().gt?.(bob, alice) + assert ord_compose_list().ge?.(bob, alice) + end - assert compare(alice, bob, ord_empty()) == :eq - assert compare(bob, alice, ord_empty()) == :eq - assert compare(alice, alice, ord_empty()) == :eq + test "compose/1 with empty list makes everything equal" do + alice = %Person{name: "Alice", age: 30} + bob = %Person{name: "Bob", age: 25} - assert ord_concat().lt?.(alice, bob) - assert ord_concat().le?.(alice, alice) - assert ord_concat().gt?.(bob, alice) - assert ord_concat().ge?.(bob, alice) + assert compare(alice, bob, ord_compose_empty()) == :eq + assert compare(bob, alice, ord_compose_empty()) == :eq + assert compare(alice, alice, ord_compose_empty()) == :eq end - test "with default ord persons (name)" do + test "compose/1 with default ord (name)" do alice = %Person{name: "Alice", age: 30} bob = %Person{name: "Bob", age: 25} - assert compare(alice, bob, ord_concat_default()) == :lt - assert compare(bob, alice, ord_concat_default()) == :gt - assert compare(alice, alice, ord_concat_default()) == :eq + assert compare(alice, bob, ord_compose_default()) == :lt + assert compare(bob, alice, ord_compose_default()) == :gt + assert compare(alice, alice, ord_compose_default()) == :eq end end @@ -739,19 +756,19 @@ defmodule Funx.OrdTest do end describe "property: monoid laws" do - property "concat with empty list is identity (all equal)" do + property "compose/1 with empty list is identity (all equal)" do check all( a <- integer(), b <- integer() ) do - empty_ord = concat([]) + empty_ord = compose([]) - # Empty concat makes everything equal + # Empty compose makes everything equal assert compare(a, b, empty_ord) == :eq end end - property "concat combines orderings lexicographically" do + property "compose/1 combines orderings lexicographically" do check all( name1 <- string(:alphanumeric, min_length: 1), name2 <- string(:alphanumeric, min_length: 1), @@ -760,7 +777,7 @@ defmodule Funx.OrdTest do ) do ord_name = contramap(& &1.name) ord_age = contramap(& &1.age) - combined = concat([ord_name, ord_age]) + combined = compose([ord_name, ord_age]) person1 = %{name: name1, age: age1} person2 = %{name: name2, age: age2} @@ -787,7 +804,7 @@ defmodule Funx.OrdTest do end end - property "append is associative" do + property "compose/2 is associative" do check all( x <- integer(), y <- integer() @@ -797,8 +814,8 @@ defmodule Funx.OrdTest do ord3 = contramap(& &1) # (ord1 + ord2) + ord3 == ord1 + (ord2 + ord3) - left = append(append(ord1, ord2), ord3) - right = append(ord1, append(ord2, ord3)) + left = compose(compose(ord1, ord2), ord3) + right = compose(ord1, compose(ord2, ord3)) assert compare(x, y, left) == compare(x, y, right) end