`case do` is not a usual macro?


Why does not work the latter case do.

iex(10)> 1 |> case do
...(10)> 1 -> 2
...(10)> _ -> 0
...(10)> end
iex(11)> (case do
...(11)> 1 -> 2
...(11)> _ -> 0
...(11)> end).(1)
** (CompileError) iex:11: undefined function case/1
1 Like

case/2 is a macro (and it is identified as such in the documentation with (macro) on the right end of the line).

Your second attempt fails because you are trying to use the anonymous function invocation syntax on case/2.

iex(1)> (fn(x) -> 
...(1)>   case x do
...(1)>     1 -> 2
...(1)>     _ -> 0
...(1)>   end
...(1)> end).(1)
iex(2)> (&(
...(2)>   case &1 do
...(2)>     1 -> 2
...(2)>     _ -> 0
...(2)>   end
...(2)> )).(1)

No, it is not. It is a special form. It does not get expanded into a different elixir AST, but directly translated into its erlang representation.

And the second snippet of the OP does not work, because case/2 does need two arguments instead of 1. Its the very same reason why (Enum.map(&(1 + &1))).([1]) does not work as well…

1 Like

There’s a documentation issue then because despite being in the special forms section case/2 is clearly identified as a (macro) - creating a contradiction.

I was surprised at the time because of the various “… didn’t need to be a special form, could have been just a macro” discussions that crop up from time to time - but there’s the (1.5) documentation saying that some things can apparently be both.

From that perspective some explanation may be required why the first snippet worked.

The first snipped worked because the pipe (|>) silently/implicitly supplied the first argument between case and do - of course the anonymous function invocation syntax doesn’t supply any arguments silently - all of them have to be clearly identified.

Thank you :slight_smile:
I got it.
I wish I could have done like this…

    |> Enum.filter(case do
                     1 -> true
                     _ -> false

Then just wrap it in a capture:

|> Enum.filter(&case &1 do 1 -> true; _ -> false)

Or even

|> Enum.filter(&Kernel.==(&1, 1))

Or, maybe slightly less obscure:

Enum.filter(&(&1 == 1))

(though the Lisp-ness of using Kernel.== explicitly is also nice, I suppose)


And possibly:

Enum.filter(&(&1 === 1))

good catch!

A special form expands to itself or another Elixir expression. For example, __MODULE__ expands to the current module atom.

From the user perspective, special forms are macros that are imported by default and are found as is in the AST. They are the building blocks of the language.

case could be implemented like this (or close to this):

defmacro case(arg, do: clauses) do
  fun = {:fn, [], clauses}
  quote do: unquote(fun).(arg)

We could even expand to that, it just would be inefficient. That doesn’t matter though. What matters about special forms is that they can’t be unimported.