View Source Bindable.ForComprehension (Bindable v1.1.0)
Elixir for-comprehension that goes beyond the List
s.
For-comprehension (its operations) could be implemented in several ways.
For example, you can desugar it into chain of flatMap
+ map
invocation:
iex> xs = [1, 2]
...> ys = [3, 4]
...> for(x <- xs, y <- ys, do: {x, y}) === Enum.flat_map(xs, fn x -> Enum.map(ys, fn y -> {x, y} end) end)
true
But if you try to implement assigns using flatMap
+ map
,
you will end up with doing something like map(ma, &{&1, assign})
,
and then each guard, that doesn't utilize every variable of such tuple,
would raise an unused variable compiler warning (filter(ma, fn {a, b} -> a > 42 end)
).
However, suppressing all these warning would be a bad idea.
They are useful to detect several kinds of flaws, while working with for-comprehension.
For example inefficient guards:
iex> import Bindable.ForComprehension
...> bindable for x <- [1, 2], y <- [3, 4], x < 2, do: {x, y}
[{1, 3}, {1, 4}]
In this case the guard does not refer to the last generated value, so the expression can be refactored to apply the guard before the last generator. It can dramatically improve performance of the whole expression in general!
iex> import Bindable.ForComprehension
...> bindable for x <- [1, 2], x < 2, y <- [3, 4], do: {x, y}
[{1, 3}, {1, 4}]
That is why flatMap
+ pure
was used for implementation,
so assigns could be implemented in a most straightforward way,
while preserving all compiler warnings. Compiler would warn you (about unused variable),
if you try to apply the guard, which does not refer to the last generated value.
Link to this section Summary
Link to this section Functions
Elixir for-comprehension that goes beyond the List
s.
Elixir does not have variadic functions or macros. But Elixir's for-comprehension looks exactly like variadic macro:
iex> for(x <- [1, 2], y <- [3, 4], do: {x, y})
[{1, 3}, {1, 4}, {2, 3}, {2, 4}]
That's because it is actually a Kernel.SpecialForms.for/1
, treated "specially" by compiler.
So to emulate variadic nature of for-comprehension (e.g. it can have one or many generators)
macro application was used (macro applied to Kernel.SpecialForms.for/1
expression):
iex> import Bindable.ForComprehension
...> bindable for x <- [1, 2], y <- [3, 4], do: {x, y}
[{1, 3}, {1, 4}, {2, 3}, {2, 4}]
It also supports Kernel.SpecialForms.for/1
-like guards and assigns.
do_for(reverse_ordered_assigns, reverse_ordered_guards, generated_value_pattern, generator, list)
View Source (macro)"Private" macro to implement for/2
.
Due to Elixir's macro expand process nature you can't have this do_for
macro as a "private" one.
It would require do_for
import at the call-site, which is impossible for "private"-macro (defined by defmacrop
).