You are previewing Erlang Programming.

Erlang Programming

Cover of Erlang Programming by Simon Thompson... Published by O'Reilly Media, Inc.
  1. Erlang Programming
    1. SPECIAL OFFER: Upgrade this ebook with O’Reilly
    2. Foreword
    3. Preface
      1. Francesco: Why Erlang?
      2. Simon: Why Erlang?
      3. Who Should Read This Book?
      4. How to Read This Book
      5. Conventions Used in This Book
      6. Using Code Examples
      7. Safari® Books Online
      8. How to Contact Us
      9. Acknowledgments
    4. 1. Introduction
      1. Why Should I Use Erlang?
      2. The History of Erlang
      3. Erlang’s Characteristics
      4. Erlang and Multicore
      5. Case Studies
      6. How Should I Use Erlang?
    5. 2. Basic Erlang
      1. Integers
      2. The Erlang Shell
      3. Floats
      4. Atoms
      5. Booleans
      6. Tuples
      7. Lists
      8. Term Comparison
      9. Variables
      10. Complex Data Structures
      11. Pattern Matching
      12. Functions
      13. Modules
      14. Exercises
    6. 3. Sequential Erlang
      1. Conditional Evaluations
      2. Guards
      3. Built-in Functions
      4. Recursion
      5. Runtime Errors
      6. Handling Errors
      7. Library Modules
      8. The Debugger
      9. Exercises
    7. 4. Concurrent Programming
      1. Creating Processes
      2. Message Passing
      3. Receiving Messages
      4. Registered Processes
      5. Timeouts
      6. Benchmarking
      7. Process Skeletons
      8. Tail Recursion and Memory Leaks
      9. A Case Study on Concurrency-Oriented Programming
      10. Race Conditions, Deadlocks, and Process Starvation
      11. The Process Manager
      12. Exercises
    8. 5. Process Design Patterns
      1. Client/Server Models
      2. A Process Pattern Example
      3. Finite State Machines
      4. Event Managers and Handlers
      5. Exercises
    9. 6. Process Error Handling
      1. Process Links and Exit Signals
      2. Robust Systems
      3. Exercises
    10. 7. Records and Macros
      1. Records
      2. Macros
      3. Exercises
    11. 8. Software Upgrade
      1. Upgrading Modules
      2. Behind the Scenes
      3. Upgrading Processes
      4. The .erlang File
      5. Exercise
    12. 9. More Data Types and High-Level Constructs
      1. Functional Programming for Real
      2. Funs and Higher-Order Functions
      3. List Comprehensions
      4. Binaries and Serialization
      5. References
      6. Exercises
    13. 10. ETS and Dets Tables
      1. ETS Tables
      2. Dets Tables
      3. A Mobile Subscriber Database Example
      4. Exercises
    14. 11. Distributed Programming in Erlang
      1. Distributed Systems in Erlang
      2. Distributed Computing in Erlang: The Basics
      3. The epmd Process
      4. Exercises
    15. 12. OTP Behaviors
      1. Introduction to OTP Behaviors
      2. Generic Servers
      3. Supervisors
      4. Applications
      5. Release Handling
      6. Other Behaviors and Further Reading
      7. Exercises
    16. 13. Introducing Mnesia
      1. When to Use Mnesia
      2. Configuring Mnesia
      3. Transactions
      4. Partitioned Networks
      5. Further Reading
      6. Exercises
    17. 14. GUI Programming with wxErlang
      1. wxWidgets
      2. wxErlang: An Erlang Binding for wxWidgets
      3. A First Example: MicroBlog
      4. The MiniBlog Example
      5. Obtaining and Running wxErlang
      6. Exercises
    18. 15. Socket Programming
      1. User Datagram Protocol
      2. Transmission Control Protocol
      3. The inet Module
      4. Further Reading
      5. Exercises
    19. 16. Interfacing Erlang with Other Programming Languages
      1. An Overview of Interworking
      2. Interworking with Java
      3. C Nodes
      4. Erlang from the Unix Shell: erl_call
      5. Port Programs
      6. Library Support for Communication
      7. Linked-in Drivers and the FFI
      8. Exercises
    20. 17. Trace BIFs, the dbg Tracer, and Match Specifications
      1. Introduction
      2. The Trace BIFs
      3. Tracing Calls with the trace_pattern BIF
      4. The dbg Tracer
      5. Match Specifications: The fun Syntax
      6. Match Specifications: The Nuts and Bolts
      7. Further Reading
      8. Exercises
    21. 18. Types and Documentation
      1. Types in Erlang
      2. TypEr: Success Types and Type Inference
      3. Documentation with EDoc
      4. Exercises
    22. 19. EUnit and Test-Driven Development
      1. Test-Driven Development
      2. EUnit
      3. The EUnit Infrastructure
      4. Testing State-Based Systems
      5. Testing Concurrent Programs in Erlang
      6. Exercises
    23. 20. Style and Efficiency
      1. Applications and Modules
      2. Processes and Concurrency
      3. Stylistic Conventions
      4. Coding Strategies
      5. Efficiency
      6. And Finally...
    24. A. Using Erlang
      1. Getting Started with Erlang
      2. Tools for Erlang
      3. Where to Learn More
    25. Index
    26. About the Authors
    27. Colophon
    28. SPECIAL OFFER: Upgrade this ebook with O’Reilly
O'Reilly logo

Receiving Messages

Messages are retrieved from the process mailbox using the receive clause. The receive clause is a construct delimited by the reserved words receive and end, and contains a number of clauses. These clauses are similar to case clauses, with a pattern in the head (to the left of the arrow) and a sequence of expressions in the body (to the right).

On executing the receive statement, the first (and oldest) message in the mailbox is pattern-matched against each pattern in the receive expression in turn:

  • If a successful match occurs, the message is retrieved from the mailbox, the variables in the pattern are bound to the matching parts of the message, and the body of the clause is executed.

  • If none of the clauses matches, the subsequent messages in the mailbox are pattern-matched one by one against all of the clauses until either a message matches a clause or all of the messages have failed all of the possible pattern matches.

In the following example, if the message {reset, 151} is sent to the process executing the receive statement, the first clause will pattern-match, resulting in the variable Board being bound to the integer 151. This will result in the function reset(151) being called:

receive
  {reset, Board} -> reset(Board);
  _Other         -> {error, unknown_msg}
end

Assume now that two new messages—restart and {reset, 151}—are received in that order by the executing process. As soon as the execution flow of the process reaches the receive statement, it will try to match the oldest message in the mailbox, restart. Pattern matching will fail in the first clause, but will match successfully in the second, binding the variable _Other to the atom restart. You can follow this example in Figure 4-3.

Selective receives

Figure 4-3. Selective receives

A receive statement will return the last evaluated expression in the body of the matched clause. In the example, this will be either the return value of the reset/1 call or the tuple {error, unknown_msg}. Although rarely done, it is possible to write expressions such as Result = receive Msg -> handle(Msg) end, in which a variable (here, Result) is bound to the return value of the receive clause. It is considered a better practice to make the receive clause the body of a separate function, and to bind the return value of the function to the variable.

Warning

Take a minute to think about the restart message in the example. Other than being a good practice, nothing is stopping us from sending the message in the format {restart}. It is a common misconception that messages have to be sent in tuples: this is not the case, as messages can consist of any valid Erlang term.

Using tuples with only one element is unnecessary, as they consume more memory and are slower to process. It is considered a bad practice to use them not only as messages, but also as terms in your programs. The guideline is that if your tuple has only one element, use the element on its own, without the tuple construct.

When none of the clauses in a case statement match, a runtime error occurs. What happens in receive statements? The syntax and semantics of receive are very similar to those of case, the main difference being that the process is suspended in receive statements until a message is matched, whereas in a case statement a runtime error occurs.

In general, a receive statement has the following form:

receive
  Pattern1 when Guard1  -> exp11, .., exp1n;
  Pattern2 when Guard2  -> exp21, .., exp2n;
  ...
  Other                 -> expn1, .., expnn
end

The keywords used to delimit a receive clause are receive and end. Each pattern consists of any valid Erlang term, including bound and unbound variables as well as optional guards. The expressions are valid Erlang terms or legal expressions that evaluate to terms. The return value of the receive clause will be the return value of the last evaluated expression in the body executed, in this case, expin.

To ensure that the receive statement always retrieves the first message in the mailbox you could use an unbound variable (such as Other in the first example) or the “don’t care” variable if you are not interested in its value.

Selective and Nonselective Receives

Look at Figure 4-4. What can you deduce from the incoming messages in the receive clause about Pid being already bound? The variable Pid is bound to the value passed to decode_digit when it is called, so it is already bound to this value in the pattern part of the receive clause. On receiving a message, a successful match will occur only if the first element of the tuple of size 2 sent as a message is exactly equal to (remember the =:= construct?) the value stored in the variable Pid.

Selective receives with bound variables

Figure 4-4. Selective receives with bound variables

We call this a selective receive, where we retrieve only the messages we are explicitly interested in based on certain criteria, leaving the remaining messages in the mailbox. Selective receives often select on process identifiers, but sequence references or other identifiers are also common, as are tagged tuples and guards. Now, contrast the bound variable Pid in Figure 4-4 with the unbound variable DigitList in Figure 4-5, which will be bound only once a message has been received.

Selective reception of multiple messages

Figure 4-5. Selective reception of multiple messages

In concurrent systems, it is common for race conditions to occur. A race condition occurs when the behavior of a system depends on the order in which certain events occur: these events “race” to influence the behavior. Due to the indeterminate nature of concurrency it is not possible to determine the order in which messages sent by different processes will arrive at the receiving process. This is when selective receive becomes crucial.

In Figure 4-5, Pid3 will receive the message foo followed by bar regardless of the order in which they are delivered. Selective receive of multiple messages is useful when synchronizing processes in a rendezvous, or when data from several sources needs to be collected before it can be processed. Contrast this with a programming language in which it is possible to process messages only in the order in which they arrive: the code for this would have to deal with the possibility of bar preceding foo and of foo preceding bar; the code becomes more complex, and much more likely to contain potential flaws.

If the order of the messages is unimportant, you can just bind them to a variable. In Figure 4-6, the first message to arrive at the process Pid3 will be processed, regardless of the order in which the messages were sent. The variable Msg in the receive statement will be bound to one of the atoms foo or bar, depending on which is delivered first.

Receipt of messages regardless of the sending order

Figure 4-6. Receipt of messages regardless of the sending order

Figure 4-7 demonstrates how processes share data with each other. The process with PidA will send a tagged tuple with its own process identifier, retrieved through a call to the BIF self(), to the process with PidB. PidB will receive it, binding PidA’s value to the variable Pid. A new tagged tuple is sent to PidC, which also pattern-matches the message in its receive statement and binds the value of PidA to the variable Pid. PidC now uses the value bound to Pid to send the message foo back to PidA. In this way, processes can share information about each other, allowing communication between processes that initially did not have knowledge of each other.

Sharing Pid data between processes

Figure 4-7. Sharing Pid data between processes

As processes do not share memory, the only way for them to share data is through message passing. Passing a message results in the data in the message being copied from the heap of the sending process to the heap of the receiving one, so this does not result in the two processes sharing a storage location (which each might read or write) but only in them each having their own copy of the data.

An Echo Example

Now that we have covered process creation and message passing, let’s use spawn, send, and receive, in a small program. Open your editor and copy the contents of Example 4-1 or download it from the book’s website. When doing so, do not forget to export the function you are spawning, in this case loop/0. In the example, pay particular notice to the fact that two different processes will be executing and interacting with each other using code defined in the same module.

Example 4-1. The echo process

-module(echo).
-export([go/0, loop/0]).

go() ->
   Pid = spawn(echo, loop, []),
   Pid ! {self(), hello},
   receive
     {Pid, Msg} ->
       io:format("~w~n",[Msg])
   end,
   Pid ! stop.


loop() ->
   receive
     {From, Msg} ->
        From ! {self(), Msg},
        loop();
     stop ->
       true
   end.

So, what does this program do? Calling the function go/0 will initiate a process whose first action is to spawn a child process. This child process starts executing in the loop/0 function and is immediately suspended in the receive clause, as its mailbox is empty. The parent, still executing in go/0, uses the Pid for the child process, which is bound as a return value from the spawn BIF, to send the child a message containing a tuple with the parent’s process identifier (given as a result of calling self()) and the atom hello.

As soon as the message is sent, the parent is suspended in a receive clause. The child, which is waiting for an incoming message, successfully pattern-matches the {Pid, Msg} tuple where Pid is matched to the process identifier belonging to the parent and Msg is matched to the atom hello. The child process uses the Pid to return the message {self(), hello} back to the parent, where this call to self() returns the pid of the child. See Figure 4-8 for a visual depiction of this process.

Sequence diagram for

Figure 4-8. Sequence diagram for Example 4-1

At this point, the parent is suspended in the receive clause, and is waiting for a message. Note that it will only pattern-match on the tuple {Pid, Msg}, where the variable Pid is already bound (as a result of the spawn BIF) to the pid of the child process. This is a good (but not entirely secure) way to ensure that the message you receive is, in fact, a message you are expecting, and not just any message consisting of a tuple with two elements sent by another process. The message arrives and is successfully pattern-matched. The atom hello is bound to the Msg variable, which is passed as an argument to the io:format/2 call, printing it out in the shell. As soon as the parent has printed the atom hello in the shell, it sends the atom stop back to the child.

What has the child been doing while the parent was busy receiving the reply and printing it? Remember that processes will terminate if they have no more code to execute, so to avoid terminating, the child called the loop/0 function recursively, suspending it in the receive clause. It receives the stop message sent to it by its parent, returns the atom true as its result, and terminates normally.

Try running the program in the shell and see what happens:

1> c(echo).
{ok,echo}
2> echo:go().
hello
stop

The atom hello is clearly the result of the io:format/2 call, but where does the atom stop come from? It is the value returned as the result of calling echo:go/0. To further familiarize yourself with concurrency, experiment with the echo example, putting io:format/2 statements in the loop/0 process and sending different messages to it. You could also experiment with the go/0 process, allowing it to send and receive more than one message. When experimenting, you will most likely get the shell to hang in a receive clause that will not match. If this happens, you will need to kill the shell and start again.



[14] The number of reductions will vary between releases. In the R12 release, the number of reductions starts at 2,000 and is reduced by one for every operation. In R13, the number of initial reductions depends on the number of scheduler threads.

The best content for your career. Discover unlimited learning on demand for around $1/day.