View Source CompareChain (compare_chain v0.6.0)

Convenience macros for doing comparisons

Valid expressions

Valid expressions for compare?/1 and compare?/2 follow these three rules:

1. A comparison operator like < must be present.

At least one of these must be included: <, >, <=, >=, ==, !=, ===, or !===. So this is valid:

compare?(1 < 2 < 3)

but this is not:

compare?(true)

because it does not contain any comparisons.

2. All arguments to boolean operators must also be valid expressions.

The boolean operators and, or, and not are allowed in expressions so long as all of their arguments (eventually) contain a comparison. So this is valid:

compare?(1 < 2 < 3 and 4 < 5)

as is this:

compare?(not (not 1 < 2 < 3))

but this is not:

compare?(1 < 2 < 3 and true)

because the right side of and fails to contain a comparison. This expression can be refactored to be valid by moving the non-comparison branch outside compare?/1 like so:

compare?(1 < 2 < 3) and true

3. The root operator of an expression must be a comparison or a boolean.

So this is not valid:

compare?(my_function(a < b), Date)

because its root operator is my_function/1. This expression can be refactored to be valid by moving compare?/2 inside my_function/1 like so:

my_function(compare?(a < b, Date))

We restrict expressions in this fashion so we can guarantee that compare?/1 and compare?/2 will always return a boolean.

Also note that arguments to comparisons may be arbitrarily complicated:

compare?(a < Date.utc_today(), Date)

Summary

Functions

Macro that performs chained comparison with operators like <.

Macro that performs chained, semantic comparison with operators like < by rewriting the expression using the compare/2 function defined by the provided module.

Functions

compare?(expr)

(macro)

Macro that performs chained comparison with operators like <.

You may also include the boolean operators and, or, and not in the expression so long as all their arguments all (eventually) contain comparisons. See the moduledoc for more details.

For a version that also does semantic comparison, see: compare?/2.

Examples

Chained comparison:

iex> import CompareChain
iex> compare?(1 < 2 < 3)
true

Comparisons joined by logical operators:

iex> import CompareChain
iex> compare?(1 >= 2 >= 3 or 4 >= 5 >= 6)
false

Warnings and errors

Comparing structs will warn

Expressions which compare matching structs like:

iex> compare?(~D[2017-03-31] < ~D[2017-04-01])
false

Will result in a warning:

... [warning] Performing structural comparison on matching structs.

Did you mean to use `compare?/2`?

  compare?(~D[2017-03-31] < ~D[2017-04-01], Date)

You probably want to use compare?/2, which does semantic comparison, instead.

Invalid expressions will raise

See the section on valid expressions in the moduledoc for details.

compare?(expr, module)

(macro)

Macro that performs chained, semantic comparison with operators like < by rewriting the expression using the compare/2 function defined by the provided module.

This is like how you can provide a module as the second argument to Enum.sort/2 when you need to sort items semantically.

You may also include the boolean operators and, or, and not in the expression so long as all their arguments all (eventually) contain comparisons. See the moduledoc for more details.

For a version that does chained comparison using the normal < operators, see: compare?/1.

Examples

Semantic comparison:

iex> import CompareChain
iex> a = ~D[2017-03-31]
iex> b = ~D[2017-04-01]
iex> compare?(a < b, Date)
true

Semantic vs. Structural Comparison Differences

In the above example, compare?(a < b, Date) evaluates to true. On its own, a < b evaluates to false (with a warning). This is why it's so important to not use comparison operators on structs directly. The answer is not what you would expect.

Trivia! If you're curious, b comes before a because in term ordering, maps of equal size are compared key by key in ascending order. In this case, :day is the first key (due to ASCII byte ordering) where a and b differ. Since a.day == 31 and b.day == 1, we have b < a.

Chained, semantic comparison:

iex> import CompareChain
iex> a = ~D[2017-03-31]
iex> b = ~D[2017-04-01]
iex> c = ~D[2017-04-02]
iex> compare?(a < b < c, Date)
true

Comparisons joined by logical operators:

iex> import CompareChain
iex> a = ~T[15:00:00]
iex> b = ~T[16:00:00]
iex> c = ~T[17:00:00]
iex> compare?(a < b and b > c, Time)
false

More complex expressions:

iex> import CompareChain
iex> compare?(%{a: ~T[16:00:00]}.a <= ~T[17:00:00], Time)
true

Custom module:

iex> import CompareChain
iex> defmodule AlwaysGreaterThan do
iex>   def compare(_left, _right), do: :gt
iex> end
iex> compare?(1 > 2 > 3, AlwaysGreaterThan)
true

Warnings and errors

Using the "strict" operators will warn

Expressions which include either === or !== like:

iex> compare?(~D[2017-03-31] !== ~D[2017-04-01], Date)
true

Will result in a warning:

... [warning] Performing semantic comparison using either: `===` or `!===`.
This is reinterpreted as `==` or `!=`, respectively.

These operators have no additional meaning over == and != when doing semantic comparison.

Invalid expressions will raise

See the section on valid expressions in the moduledoc for details.