I can do:
if true, do: :foo, else: :bar
and:
if true do
:foo
else
:bar
end
And I can do:
for i <- 0..3, do: {i, i+i}, into: %{}
but not:
for i <- 0..3 do
{i, i+i}
into
%{}
end
why is that?
I can do:
if true, do: :foo, else: :bar
and:
if true do
:foo
else
:bar
end
And I can do:
for i <- 0..3, do: {i, i+i}, into: %{}
but not:
for i <- 0..3 do
{i, i+i}
into
%{}
end
why is that?
Because else
got special treatment to ease user experience. This sugar is not available for any other atom outside the small set of predefined ones (else
, rescue
, catch
, and after
).
So I can’t use into
, uniq
, reduce
in a for ... end
block?
You can, you have to pass them before the do.
for …, into: %{} do
…
end
if true, do: :foo, else: :bar
and:
if true do
:foo
else
:bar
end
are different, although they look similar.
The first form is actually a if
macro accepting two arguments.
if(true, [do: :foo, else: :bar])
And to make the second form works, we need a into
macro which I don’t think we have it.
That is not true. Both are exactly the same thing for the compiler:
iex(1)> quote do
...(1)> if true do
...(1)> :foo
...(1)> else
...(1)> :bar
...(1)> end
...(1)> end
{:if, [context: Elixir, import: Kernel], [true, [do: :foo, else: :bar]]}
iex(2)> quote do
...(2)> if(true, [do: :foo, else: :bar])
...(2)> end
{:if, [context: Elixir, import: Kernel], [true, [do: :foo, else: :bar]]}
Yes they are the same. A do
block after a call adds the [do: ...]
argument to the function/macro call on the left.
So if you call this (notice the parentheses for the if
call)
if(a == 1) do
:ok
end
The compiler kind of rewrites it to if(a == 1, [do: :ok])
This works for functions too:
defmodule Test do
def myfun(value, block) do
value |> IO.inspect(label: "value")
block |> IO.inspect(label: "block")
end
end
Test.myfun :some_value do
a = 1
b = 2
a + b
end
This will output the following:
value: :some_value
block: [do: 3]
But as myfun/2
is a function and not a macro, the block is evaluated before being passed to the function, hence you see [do: 3]
, whereas with macros the quoted block is passed. This syntactic sugar is then less useful for functions.
The for
macro is special, because if you pass into: %{}, do: stuff()
, the do
and into
are in the same keyword list ; if you pass into: %{}
and then a do end
block, the compiler will add another argument to the for()
call, containing just the do
block:
iex(1)> quote do
...(1)> for a <- 1..2, into: %{} do
...(1)> {a, a*a}
...(1)> end
...(1)> end
{:for, [],
[
{:<-, [],
[{:a, [], Elixir}, {:.., [context: Elixir, import: Kernel], [1, 2]}]},
[into: {:%{}, [], []}],
[
do: {{:a, [], Elixir},
{:*, [context: Elixir, import: Kernel],
[{:a, [], Elixir}, {:a, [], Elixir}]}}
]
]}
iex(2)> quote do
...(2)> for a <- 1..2, into: %{}, do: {a, a*a}
...(2)> end
{:for, [],
[
{:<-, [],
[{:a, [], Elixir}, {:.., [context: Elixir, import: Kernel], [1, 2]}]},
[
into: {:%{}, [], []},
do: {{:a, [], Elixir},
{:*, [context: Elixir, import: Kernel],
[{:a, [], Elixir}, {:a, [], Elixir}]}}
]
]}
I guess that is why for
is defined in Kernel.SpecialForms
as an error because it is handled at the compiler level.
Well, no. It is still consistent behaviour among all other calls:
quote do
foo bar: 1, do: 10
end
# => {:foo, [], [[bar: 1, do: 10]]}
quote do
foo bar: 1 do
10
end
end
# => {:foo, [], [[bar: 1], [do: 10]]}
There are 2 reasons why for
is special form and not “regular” macro:
<<a <- binary>>
syntax need special handlingSo all of the above forces the Elixir to treat for
as a “special thing” within parser itself.
I don’t understand to what you say “no”.
I mean in your example it calls two different foo
functions. The for
construct handles that in a special way, there are not several definitions for for
to my knowledge in the Elixir code.
As I said, it is literally impossible to define for
in Elixir. I meant that do: 10
and do … end
aren’t really treated differently just for for
, but the same rules applies to all calls.
for
is special form not because of do
but because each x <- y
is separate argument, so for i <- 1..10, do: i
is 2-ary function and for i <- 1..10, j <- 1..10, do: i + j
is 3-ary function. So either for
would need to have N different heads and just fail in case if someone would like to use more than that, then it would fail (see older Rust compilers to see that problem, there Debug
is defined only up to 32-ary tuples, as there is no variadic generics). Due that problem for
and with
must be defined as special forms, because they require parser magic to be then expanded properly.
Ok so it is not defined as a special form just for the arity problem, but otherwise it is just what I said, basically it is a special form because it requires special treatment at the compiler level.