2
votes

I'm currently trying my hand at Elixir. I have very little experience with either Ruby or functional programming so I'm not too familiar with the syntax. I was reading Learn Elixir in Y minutes and I was a bit confused by one of the examples. At first, the guide showed the case control-flow structure, which I understood just fine.

case {:one, :two} do
  {:four, :five} ->
    "This won't match"
  {:one, x} ->
    "This will match and bind `x` to `:two` in this clause"
  _ ->
    "This will match any value"
end

However, one of the last examples that was shown was one about recieving messages from other processes. It had a very similar syntax and structure to the case example but it did not use the case keyword. It looks to me like some sort of anonymous case syntax for use with functions which can take differing arguments.

defmodule Geometry do
  def area_loop do
    receive do
      {:rectangle, w, h} ->
        IO.puts("Area = #{w * h}")
        area_loop()
      {:circle, r} ->
        IO.puts("Area = #{3.14 * r * r}")
        area_loop()
    end
  end
end

What is the difference between these two syntaxes?

3

3 Answers

4
votes

The case syntax takes an argument, which is the thing to do the comparison on. receive does not take an argument, but instead allows you to match on the message that the process has received.

Check out the docs on the receive function.

One difference is that case will complain if it cannot match:

a = 1
case a do
  2 ->
    IO.puts("2")
end

This will show this exception:

** (CaseClauseError) no case clause matching: 1

The receive function will not complain if it receives a message that doesn't match any of its clauses. It will simply ignore that message. The message is returned to that process's mailbox in case there is another receive block which may process that message.

Another difference is that receive can time out after a certain time (as shown in the docs):

receive do
  {:selector, i, value} when is_integer(i) ->
    value
  value when is_atom(value) ->
    value
  _ ->
    IO.puts :stderr, "Unexpected message received"
after
  5000 ->
    IO.puts :stderr, "No message in 5 seconds"
end

This can be helpful if you want to ensure that a process receives a message in a timely fashion.

This information can also be found on page 165 of the Elixir in Action book.

2
votes

This syntax - similar to what you can find in case - is all over elixir. It's the basic pattern matching syntax that you can find in all construct allowing pattern matching.

One of them is case, another one is receive, yet another is fn.

  • case allows to pattern match on a variable that you pass to it.
  • receive is for pattern matching on messages sent to the process - the important thing is that receive is selective. A single receive will process only one message matching one of the patterns. All the messages that do not match any of the patterns will be stored for later processing. This can be dangerous if you leave a lot of unprocessed messages in the mailbox - scanning the messages is linear to the number of messages in the mailbox, with too many messages not matching any of the patterns it can take a really long time to look through them.
  • fn defines an anonymous function - many people don't realise you can pattern match in them, and define multiple clauses too.

for example:

fn
  {:ok, foo}       -> foo
  {:error, reason} -> raise "processing error: #{inspect error}"
end
1
votes

In addition to Ryan's answer above, as per my understanding, receive and case are two completely different things in Elixir(/erlang).

case..end is a standard block construct, while receive is an atom, and is handled in erts/emulator/beam/beam_emu.c, look for text "receive statement" in the file. The code it expects seems to originate in lib/compiler/src/beam_receive.erl.