Consider the following code:
iex|1 ▶ [:foo, :bar] -- [:foo, :bar]
#⇒ []
So far, so good. But:
iex|2 ▶ [:foo, :bar] -- [] -- [:foo, :bar]
#⇒ [:foo, :bar]
Even more, it’s not about right-to-left:
iex|3 ▶ [:foo, :bar] -- [:foo] -- [:foo, :bar]
#⇒ [:foo, :bar]
iex|4 ▶ IO.inspect([:foo, :bar], label: "1") --
...|4 ▶ IO.inspect([:foo], label: "2") --
...|4 ▶ IO.inspect([:foo, :bar], label: "3")
#⇒ 1: [:foo, :bar]
# 2: [:foo]
# 3: [:foo, :bar]
#⇒ [:foo, :bar]
Am I missing something obvious? What’s going on here? There should not be any magic, since Kernel.--/2
is simply delegating to :erlang.--(left, right)
.
Why consecutive subtracting lists results in noop?
FWIW, with parentheses everything works as expected:
iex|5 ▶ ([:foo, :bar] -- [:foo]) -- [:foo, :bar]
#⇒ []
More fun:
iex|6 ▶ [:foo, :bar] -- [:foo] -- []
#⇒ [:bar]
iex|7 ▶ [:foo, :bar] -- [:foo] -- [:foo]
#⇒ [:foo, :bar]
iex|8 ▶ [:foo, :bar] -- [:foo] -- [:bar]
#⇒ [:bar]
Follow-up findings. Short-form reducing somehow manages to follow right-associative semantics:
Enum.reduce([[:foo, :bar], [:foo], [:foo, :bar]], &Kernel.--/2)
#⇒ [:foo, :bar]
But the full well-formed one with an explicit function does not
Enum.reduce(
[[:foo, :bar], [:foo], [:foo, :bar]],
fn e, acc -> acc -- e end
)
#⇒ []
++
and --
are right-associative operations. Your initial code:
[:foo, :bar] -- [:foo] -- [:foo, :bar]
Is actually evaluated as
[:foo, :bar] -- ([:foo] -- [:foo, :bar])
When you take what's in the brackets: you remove a list of elements from [:foo]
, and the list you remove contains the original list ([:foo]
) thus evaluates to an empty list []
.
Then you remove this empty list from the leftmost list:
[:foo, :bar] -- []
Which leaves you with the result [:foo, :bar]
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With