diff options
Diffstat (limited to 'system/doc/getting_started')
-rw-r--r-- | system/doc/getting_started/Makefile | 100 | ||||
-rw-r--r-- | system/doc/getting_started/book.xml | 43 | ||||
-rw-r--r-- | system/doc/getting_started/conc_prog.xml | 894 | ||||
-rw-r--r-- | system/doc/getting_started/intro.xml | 66 | ||||
-rw-r--r-- | system/doc/getting_started/make.dep | 14 | ||||
-rw-r--r-- | system/doc/getting_started/part.xml | 36 | ||||
-rw-r--r-- | system/doc/getting_started/records_macros.xml | 328 | ||||
-rw-r--r-- | system/doc/getting_started/robustness.xml | 483 | ||||
-rw-r--r-- | system/doc/getting_started/seq_prog.xml | 1231 | ||||
-rw-r--r-- | system/doc/getting_started/xmlfiles.mk | 24 |
10 files changed, 3219 insertions, 0 deletions
diff --git a/system/doc/getting_started/Makefile b/system/doc/getting_started/Makefile new file mode 100644 index 0000000000..5ca885d56e --- /dev/null +++ b/system/doc/getting_started/Makefile @@ -0,0 +1,100 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1996-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include $(ERL_TOP)/erts/vsn.mk +#VSN=$(SYSTEM_VSN) + +APPLICATION=otp-system-documentation +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/doc/getting_started + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +XML_PART_FILES = part.xml + +include xmlfiles.mk + +XML_CHAPTER_FILES = $(GETTING_STARTED_CHAPTER_FILES) + +TOPDOCDIR=.. + +BOOK_FILES = book.xml + +GIF_FILES = + +PS_FILES = $(GIF_FILES:%.gif=%.ps) + +XML_FILES = \ + $(BOOK_FILES) $(XML_CHAPTER_FILES) \ + $(XML_PART_FILES) +# ---------------------------------------------------- + +HTML_FILES = \ + $(XML_PART_FILES:%.xml=%.html) + +HTMLDIR = ../html/getting_started + + +HTML_UG_FILE = $(HTMLDIR)/users_guide.html + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +XML_FLAGS += +DVIPS_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +docs: html + +local_docs: PDFDIR=../../pdf + +html: $(GIF_FILES) $(HTML_UG_FILE) + +debug opt: + +clean clean_docs: + rm -rf $(HTMLDIR) + rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f errs core *~ + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_docs_spec: docs +# $(INSTALL_DIR) $(RELEASE_PATH)/pdf +# $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELEASE_PATH)/pdf + $(INSTALL_DIR) $(RELSYSDIR) + $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \ + $(RELSYSDIR) + +release_spec: diff --git a/system/doc/getting_started/book.xml b/system/doc/getting_started/book.xml new file mode 100644 index 0000000000..c256dc1317 --- /dev/null +++ b/system/doc/getting_started/book.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE book SYSTEM "book.dtd"> + +<book xmlns:xi="http://www.w3.org/2001/XInclude"> + <header titlestyle="normal"> + <copyright> + <year>1996</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>Getting Started with Erlang</title> + <prepared>Mike Williams</prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>book.sgml</file> + </header> + <insidecover> + </insidecover> + <pagetext>Getting Started with Erlang</pagetext> + <preamble> + <contents level="2"></contents> + </preamble> + <parts lift="no"> + <xi:include href="part.xml"/> + </parts> + <listofterms></listofterms> + <index></index> +</book> + diff --git a/system/doc/getting_started/conc_prog.xml b/system/doc/getting_started/conc_prog.xml new file mode 100644 index 0000000000..34ae428b2c --- /dev/null +++ b/system/doc/getting_started/conc_prog.xml @@ -0,0 +1,894 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2003</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>Concurrent Programming</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>conc_prog.xml</file> + </header> + + <section> + <title>Processes</title> + <p>One of the main reasons for using Erlang instead of other + functional languages is Erlang's ability to handle concurrency + and distributed programming. By concurrency we mean programs + which can handle several threads of execution at the same time. + For example, modern operating systems would allow you to use a + word processor, a spreadsheet, a mail client and a print job all + running at the same time. Of course each processor (CPU) in + the system is probably only handling one thread (or job) at a + time, but it swaps between the jobs a such a rate that it gives + the illusion of running them all at the same time. It is easy to + create parallel threads of execution in an Erlang program and it + is easy to allow these threads to communicate with each other. In + Erlang we call each thread of execution a <em>process</em>.</p> + <p>(Aside: the term "process" is usually used when the threads of + execution share no data with each other and the term "thread" + when they share data in some way. Threads of execution in Erlang + share no data, that's why we call them processes).</p> + <p>The Erlang BIF <c>spawn</c> is used to create a new process: + <c>spawn(Module, Exported_Function, List of Arguments)</c>. + Consider the following module:</p> + <code type="none"> +-module(tut14). + +-export([start/0, say_something/2]). + +say_something(What, 0) -> + done; +say_something(What, Times) -> + io:format("~p~n", [What]), + say_something(What, Times - 1). + +start() -> + spawn(tut14, say_something, [hello, 3]), + spawn(tut14, say_something, [goodbye, 3]).</code> + <pre> +5> <input>c(tut14).</input> +{ok,tut14} +6> <input>tut14:say_something(hello, 3).</input> +hello +hello +hello +done</pre> + <p>We can see that function <c>say_something</c> writes its first + argument the number of times specified by second argument. Now + look at the function <c>start</c>. It starts two Erlang processes, + one which writes "hello" three times and one which writes + "goodbye" three times. Both of these processes use the function + <c>say_something</c>. Note that a function used in this way by + <c>spawn</c> to start a process must be exported from the module + (i.e. in the <c>-export</c> at the start of the module).</p> + <pre> +9> <input>tut14:start().</input> +hello +goodbye +<0.63.0> +hello +goodbye +hello +goodbye</pre> + <p>Notice that it didn't write "hello" three times and then + "goodbye" three times, but the first process wrote a "hello", + the second a "goodbye", the first another "hello" and so forth. + But where did the <0.63.0> come from? The return value of a + function is of course the return value of the last "thing" in + the function. The last thing in the function <c>start</c> is</p> + <code type="none"> +spawn(tut14, say_something, [goodbye, 3]).</code> + <p><c>spawn</c> returns a <em>process identifier</em>, or + <em>pid</em>, which uniquely identifies the process. So <0.63.0> + is the pid of the <c>spawn</c> function call above. We will see + how to use pids in the next example.</p> + <p>Note as well that we have used ~p instead of ~w in + <c>io:format</c>. To quote the manual: "~p Writes the data with + standard syntax in the same way as ~w, but breaks terms whose + printed representation is longer than one line into many lines + and indents each line sensibly. It also tries to detect lists of + printable characters and to output these as strings".</p> + </section> + + <section> + <title>Message Passing</title> + <p>In the following example we create two processes which send + messages to each other a number of times.</p> + <code type="none"> +-module(tut15). + +-export([start/0, ping/2, pong/0]). + +ping(0, Pong_PID) -> + Pong_PID ! finished, + io:format("ping finished~n", []); + +ping(N, Pong_PID) -> + Pong_PID ! {ping, self()}, + receive + pong -> + io:format("Ping received pong~n", []) + end, + ping(N - 1, Pong_PID). + +pong() -> + receive + finished -> + io:format("Pong finished~n", []); + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong() + end. + +start() -> + Pong_PID = spawn(tut15, pong, []), + spawn(tut15, ping, [3, Pong_PID]).</code> + <pre> +1> <input>c(tut15).</input> +{ok,tut15} +2> <input>tut15: start().</input> +<0.36.0> +Pong received ping +Ping received pong +Pong received ping +Ping received pong +Pong received ping +Ping received pong +ping finished +Pong finished</pre> + <p>The function <c>start</c> first creates a process, let's call it + "pong":</p> + <code type="none"> +Pong_PID = spawn(tut15, pong, [])</code> + <p>This process executes <c>tut15:pong()</c>. <c>Pong_PID</c> is + the process identity of the "pong" process. The function + <c>start</c> now creates another process "ping".</p> + <code type="none"> +spawn(tut15, ping, [3, Pong_PID]),</code> + <p>this process executes</p> + <code type="none"> +tut15:ping(3, Pong_PID)</code> + <p><0.36.0> is the return value from the <c>start</c> function.</p> + <p>The process "pong" now does:</p> + <code type="none"> +receive + finished -> + io:format("Pong finished~n", []); + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong() +end.</code> + <p>The <c>receive</c> construct is used to allow processes to wait + for messages from other processes. It has the format:</p> + <code type="none"> +receive + pattern1 -> + actions1; + pattern2 -> + actions2; + .... + patternN + actionsN +end.</code> + <p>Note: no ";" before the <c>end</c>.</p> + <p>Messages between Erlang processes are simply valid Erlang terms. + I.e. they can be lists, tuples, integers, atoms, pids etc.</p> + <p>Each process has its own input queue for messages it receives. + New messages received are put at the end of the queue. When a + process executes a <c>receive</c>, the first message in the queue + is matched against the first pattern in the <c>receive</c>, if + this matches, the message is removed from the queue and + the actions corresponding to the the pattern are executed.</p> + <p>However, if the first pattern does not match, the second pattern + is tested, if this matches the message is removed from the queue + and the actions corresponding to the second pattern are executed. + If the second pattern does not match the third is tried and so on + until there are no more pattern to test. If there are no more + patterns to test, the first message is kept in the queue and we + try the second message instead. If this matches any pattern, + the appropriate actions are executed and the second message is + removed from the queue (keeping the first message and any other + messages in the queue). If the second message does not match we + try the third message and so on until we reach the end of + the queue. If we reach the end of the queue, the process blocks + (stops execution) and waits until a new message is received and + this procedure is repeated.</p> + <p>Of course the Erlang implementation is "clever" and minimizes + the number of times each message is tested against the patterns + in each <c>receive</c>.</p> + <p>Now back to the ping pong example.</p> + <p>"Pong" is waiting for messages. If the atom <c>finished</c> is + received, "pong" writes "Pong finished" to the output and as it + has nothing more to do, terminates. If it receives a message with + the format:</p> + <code type="none"> +{ping, Ping_PID}</code> + <p>it writes "Pong received ping" to the output and sends the atom + <c>pong</c> to the process "ping":</p> + <code type="none"> +Ping_PID ! pong</code> + <p>Note how the operator "!" is used to send messages. The syntax + of "!" is:</p> + <code type="none"> +Pid ! Message</code> + <p>I.e. <c>Message</c> (any Erlang term) is sent to the process + with identity <c>Pid</c>.</p> + <p>After sending the message <c>pong</c>, to the process "ping", + "pong" calls the <c>pong</c> function again, which causes it to + get back to the <c>receive</c> again and wait for another message. + Now let's look at the process "ping". Recall that it was started + by executing:</p> + <code type="none"> +tut15:ping(3, Pong_PID)</code> + <p>Looking at the function <c>ping/2</c> we see that the second + clause of <c>ping/2</c> is executed since the value of the first + argument is 3 (not 0) (first clause head is + <c>ping(0,Pong_PID)</c>, second clause head is + <c>ping(N,Pong_PID)</c>, so <c>N</c> becomes 3).</p> + <p>The second clause sends a message to "pong":</p> + <code type="none"> +Pong_PID ! {ping, self()},</code> + <p><c>self()</c> returns the pid of the process which executes + <c>self()</c>, in this case the pid of "ping". (Recall the code + for "pong", this will land up in the variable <c>Ping_PID</c> in + the <c>receive</c> previously explained).</p> + <p>"Ping" now waits for a reply from "pong":</p> + <code type="none"> +receive + pong -> + io:format("Ping received pong~n", []) +end,</code> + <p>and writes "Ping received pong" when this reply arrives, after + which "ping" calls the <c>ping</c> function again.</p> + <code type="none"> +ping(N - 1, Pong_PID)</code> + <p><c>N-1</c> causes the first argument to be decremented until it + becomes 0. When this occurs, the first clause of <c>ping/2</c> + will be executed:</p> + <code type="none"> +ping(0, Pong_PID) -> + Pong_PID ! finished, + io:format("ping finished~n", []);</code> + <p>The atom <c>finished</c> is sent to "pong" (causing it to + terminate as described above) and "ping finished" is written to + the output. "Ping" then itself terminates as it has nothing left + to do.</p> + </section> + + <section> + <title>Registered Process Names</title> + <p>In the above example, we first created "pong" so as to be able + to give the identity of "pong" when we started "ping". I.e. in + some way "ping" must be able to know the identity of "pong" in + order to be able to send a message to it. Sometimes processes + which need to know each others identities are started completely + independently of each other. Erlang thus provides a mechanism for + processes to be given names so that these names can be used as + identities instead of pids. This is done by using + the <c>register</c> BIF:</p> + <code type="none"> +register(some_atom, Pid)</code> + <p>We will now re-write the ping pong example using this and giving + the name <c>pong</c> to the "pong" process:</p> + <code type="none"> +-module(tut16). + +-export([start/0, ping/1, pong/0]). + +ping(0) -> + pong ! finished, + io:format("ping finished~n", []); + +ping(N) -> + pong ! {ping, self()}, + receive + pong -> + io:format("Ping received pong~n", []) + end, + ping(N - 1). + +pong() -> + receive + finished -> + io:format("Pong finished~n", []); + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong() + end. + +start() -> + register(pong, spawn(tut16, pong, [])), + spawn(tut16, ping, [3]).</code> + <pre> +2> <input>c(tut16).</input> +{ok, tut16} +3> <input>tut16:start().</input> +<0.38.0> +Pong received ping +Ping received pong +Pong received ping +Ping received pong +Pong received ping +Ping received pong +ping finished +Pong finished</pre> + <p>In the <c>start/0</c> function,</p> + <code type="none"> +register(pong, spawn(tut16, pong, [])),</code> + <p>both spawns the "pong" process and gives it the name <c>pong</c>. + In the "ping" process we can now send messages to <c>pong</c> by:</p> + <code type="none"> +pong ! {ping, self()},</code> + <p>so that <c>ping/2</c> now becomes <c>ping/1</c> as we don't have + to use the argument <c>Pong_PID</c>.</p> + </section> + + <section> + <title>Distributed Programming</title> + <p>Now let's re-write the ping pong program with "ping" and "pong" + on different computers. Before we do this, there are a few things + we need to set up to get this to work. The distributed Erlang + implementation provides a basic security mechanism to prevent + unauthorized access to an Erlang system on another computer + (*manual*). Erlang systems which talk to each other must have + the same <em>magic cookie</em>. The easiest way to achieve this + is by having a file called <c>.erlang.cookie</c> in your home + directory on all machines which on which you are going to run + Erlang systems communicating with each other (on Windows systems + the home directory is the directory where pointed to by the $HOME + environment variable - you may need to set this. On Linux or Unix + you can safely ignore this and simply create a file called + <c>.erlang.cookie</c> in the directory you get to after executing + the command <c>cd</c> without any argument). + The <c>.erlang.cookie</c> file should contain on line with + the same atom. For example on Linux or Unix in the OS shell:</p> + <pre> +$ <input>cd</input> +$ <input>cat > .erlang.cookie</input> +this_is_very_secret +$ <input>chmod 400 .erlang.cookie</input></pre> + <p>The <c>chmod</c> above make the <c>.erlang.cookie</c> file + accessible only by the owner of the file. This is a requirement.</p> + <p>When you start an Erlang system which is going to talk to other + Erlang systems, you must give it a name, eg: </p> + <pre> +$ <input>erl -sname my_name</input></pre> + <p>We will see more details of this later (*manual*). If you want to + experiment with distributed Erlang, but you only have one + computer to work on, you can start two separate Erlang systems on + the same computer but give them different names. Each Erlang + system running on a computer is called an Erlang node.</p> + <p>(Note: <c>erl -sname</c> assumes that all nodes are in the same + IP domain and we can use only the first component of the IP + address, if we want to use nodes in different domains we use + <c>-name</c> instead, but then all IP address must be given in + full (*manual*).</p> + <p>Here is the ping pong example modified to run on two separate + nodes:</p> + <code type="none"> +-module(tut17). + +-export([start_ping/1, start_pong/0, ping/2, pong/0]). + +ping(0, Pong_Node) -> + {pong, Pong_Node} ! finished, + io:format("ping finished~n", []); + +ping(N, Pong_Node) -> + {pong, Pong_Node} ! {ping, self()}, + receive + pong -> + io:format("Ping received pong~n", []) + end, + ping(N - 1, Pong_Node). + +pong() -> + receive + finished -> + io:format("Pong finished~n", []); + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong() + end. + +start_pong() -> + register(pong, spawn(tut17, pong, [])). + +start_ping(Pong_Node) -> + spawn(tut17, ping, [3, Pong_Node]).</code> + <p>Let us assume we have two computers called gollum and kosken. We + will start a node on kosken called ping and then a node on gollum + called pong.</p> + <p>On kosken (on a Linux/Unix system):</p> + <pre> +kosken> <input>erl -sname ping</input> +Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0] + +Eshell V5.2.3.7 (abort with ^G) +(ping@kosken)1></pre> + <p>On gollum:</p> + <pre> +gollum> <input>erl -sname pong</input> +Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0] + +Eshell V5.2.3.7 (abort with ^G) +(pong@gollum)1></pre> + <p>Now we start the "pong" process on gollum:</p> + <pre> +(pong@gollum)1> <input>tut17:start_pong().</input> +true</pre> + <p>and start the "ping" process on kosken (from the code above you + will see that a parameter of the <c>start_ping</c> function is + the node name of the Erlang system where "pong" is running):</p> + <pre> +(ping@kosken)1> <input>tut17:start_ping(pong@gollum).</input> +<0.37.0> +Ping received pong +Ping received pong +Ping received pong +ping finished</pre> + <p>Here we see that the ping pong program has run, on the "pong" + side we see:</p> + <pre> +(pong@gollum)2> +Pong received ping +Pong received ping +Pong received ping +Pong finished +(pong@gollum)2></pre> + <p>Looking at the <c>tut17</c> code we see that the <c>pong</c> + function itself is unchanged, the lines:</p> + <code type="none"> +{ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong,</code> + <p>work in the same way irrespective of on which node the "ping" + process is executing. Thus Erlang pids contain information about + where the process executes so if you know the pid of a process, + the "!" operator can be used to send it a message if the process + is on the same node or on a different node.</p> + <p>A difference is how we send messages to a registered process on + another node:</p> + <code type="none"> +{pong, Pong_Node} ! {ping, self()},</code> + <p>We use a tuple <c>{registered_name,node_name}</c> instead of + just the <c>registered_name</c>.</p> + <p>In the previous example, we started "ping" and "pong" from + the shells of two separate Erlang nodes. <c>spawn</c> can also be + used to start processes in other nodes. The next example is + the ping pong program, yet again, but this time we will start + "ping" in another node:</p> + <code type="none"> +-module(tut18). + +-export([start/1, ping/2, pong/0]). + +ping(0, Pong_Node) -> + {pong, Pong_Node} ! finished, + io:format("ping finished~n", []); + +ping(N, Pong_Node) -> + {pong, Pong_Node} ! {ping, self()}, + receive + pong -> + io:format("Ping received pong~n", []) + end, + ping(N - 1, Pong_Node). + +pong() -> + receive + finished -> + io:format("Pong finished~n", []); + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong() + end. + +start(Ping_Node) -> + register(pong, spawn(tut18, pong, [])), + spawn(Ping_Node, tut18, ping, [3, node()]).</code> + <p>Assuming an Erlang system called ping (but not the "ping" + process) has already been started on kosken, then on gollum we do:</p> + <pre> +(pong@gollum)1> <input>tut18:start(ping@kosken).</input> +<3934.39.0> +Pong received ping +Ping received pong +Pong received ping +Ping received pong +Pong received ping +Ping received pong +Pong finished +ping finished</pre> + <p>Notice we get all the output on gollum. This is because the io + system finds out where the process is spawned from and sends all + output there.</p> + </section> + + <section> + <title>A Larger Example</title> + <p>Now for a larger example. We will make an extremely simple + "messenger". The messenger is a program which allows users to log + in on different nodes and send simple messages to each other.</p> + <p>Before we start, let's note the following:</p> + <list type="bulleted"> + <item> + <p>This example will just show the message passing logic no + attempt at all has been made to provide a nice graphical user + interface - this can of course also be done in Erlang - but + that's another tutorial.</p> + </item> + <item> + <p>This sort of problem can be solved more easily if you use + the facilities in OTP, which will also provide methods for + updating code on the fly etc. But again, that's another + tutorial.</p> + </item> + <item> + <p>The first program we write will contain some inadequacies as + regards handling of nodes which disappear, we will correct + these in a later version of the program.</p> + </item> + </list> + <p>We will set up the messenger by allowing "clients" to connect to + a central server and say who and where they are. I.e. a user + won't need to know the name of the Erlang node where another user + is located to send a message.</p> + <p>File <c>messenger.erl</c>:</p> + <marker id="ex"></marker> + <code type="none"> +%%% Message passing utility. +%%% User interface: +%%% logon(Name) +%%% One user at a time can log in from each Erlang node in the +%%% system messenger: and choose a suitable Name. If the Name +%%% is already logged in at another node or if someone else is +%%% already logged in at the same node, login will be rejected +%%% with a suitable error message. +%%% logoff() +%%% Logs off anybody at at node +%%% message(ToName, Message) +%%% sends Message to ToName. Error messages if the user of this +%%% function is not logged on or if ToName is not logged on at +%%% any node. +%%% +%%% One node in the network of Erlang nodes runs a server which maintains +%%% data about the logged on users. The server is registered as "messenger" +%%% Each node where there is a user logged on runs a client process registered +%%% as "mess_client" +%%% +%%% Protocol between the client processes and the server +%%% ---------------------------------------------------- +%%% +%%% To server: {ClientPid, logon, UserName} +%%% Reply {messenger, stop, user_exists_at_other_node} stops the client +%%% Reply {messenger, logged_on} logon was successful +%%% +%%% To server: {ClientPid, logoff} +%%% Reply: {messenger, logged_off} +%%% +%%% To server: {ClientPid, logoff} +%%% Reply: no reply +%%% +%%% To server: {ClientPid, message_to, ToName, Message} send a message +%%% Reply: {messenger, stop, you_are_not_logged_on} stops the client +%%% Reply: {messenger, receiver_not_found} no user with this name logged on +%%% Reply: {messenger, sent} Message has been sent (but no guarantee) +%%% +%%% To client: {message_from, Name, Message}, +%%% +%%% Protocol between the "commands" and the client +%%% ---------------------------------------------- +%%% +%%% Started: messenger:client(Server_Node, Name) +%%% To client: logoff +%%% To client: {message_to, ToName, Message} +%%% +%%% Configuration: change the server_node() function to return the +%%% name of the node where the messenger server runs + +-module(messenger). +-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]). + +%%% Change the function below to return the name of the node where the +%%% messenger server runs +server_node() -> + messenger@bill. + +%%% This is the server process for the "messenger" +%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...] +server(User_List) -> + receive + {From, logon, Name} -> + New_User_List = server_logon(From, Name, User_List), + server(New_User_List); + {From, logoff} -> + New_User_List = server_logoff(From, User_List), + server(New_User_List); + {From, message_to, To, Message} -> + server_transfer(From, To, Message, User_List), + io:format("list is now: ~p~n", [User_List]), + server(User_List) + end. + +%%% Start the server +start_server() -> + register(messenger, spawn(messenger, server, [[]])). + + +%%% Server adds a new user to the user list +server_logon(From, Name, User_List) -> + %% check if logged on anywhere else + case lists:keymember(Name, 2, User_List) of + true -> + From ! {messenger, stop, user_exists_at_other_node}, %reject logon + User_List; + false -> + From ! {messenger, logged_on}, + [{From, Name} | User_List] %add user to the list + end. + +%%% Server deletes a user from the user list +server_logoff(From, User_List) -> + lists:keydelete(From, 1, User_List). + + +%%% Server transfers a message between user +server_transfer(From, To, Message, User_List) -> + %% check that the user is logged on and who he is + case lists:keysearch(From, 1, User_List) of + false -> + From ! {messenger, stop, you_are_not_logged_on}; + {value, {From, Name}} -> + server_transfer(From, Name, To, Message, User_List) + end. +%%% If the user exists, send the message +server_transfer(From, Name, To, Message, User_List) -> + %% Find the receiver and send the message + case lists:keysearch(To, 2, User_List) of + false -> + From ! {messenger, receiver_not_found}; + {value, {ToPid, To}} -> + ToPid ! {message_from, Name, Message}, + From ! {messenger, sent} + end. + + +%%% User Commands +logon(Name) -> + case whereis(mess_client) of + undefined -> + register(mess_client, + spawn(messenger, client, [server_node(), Name])); + _ -> already_logged_on + end. + +logoff() -> + mess_client ! logoff. + +message(ToName, Message) -> + case whereis(mess_client) of % Test if the client is running + undefined -> + not_logged_on; + _ -> mess_client ! {message_to, ToName, Message}, + ok +end. + + +%%% The client process which runs on each server node +client(Server_Node, Name) -> + {messenger, Server_Node} ! {self(), logon, Name}, + await_result(), + client(Server_Node). + +client(Server_Node) -> + receive + logoff -> + {messenger, Server_Node} ! {self(), logoff}, + exit(normal); + {message_to, ToName, Message} -> + {messenger, Server_Node} ! {self(), message_to, ToName, Message}, + await_result(); + {message_from, FromName, Message} -> + io:format("Message from ~p: ~p~n", [FromName, Message]) + end, + client(Server_Node). + +%%% wait for a response from the server +await_result() -> + receive + {messenger, stop, Why} -> % Stop the client + io:format("~p~n", [Why]), + exit(normal); + {messenger, What} -> % Normal response + io:format("~p~n", [What]) + end.</code> + <p>To use this program you need to:</p> + <list type="bulleted"> + <item>configure the <c>server_node()</c> function</item> + <item>copy the compiled code (<c>messenger.beam</c>) to + the directory on each computer where you start Erlang.</item> + </list> + <p>In the following example of use of this program, I have started + nodes on four different computers, but if you don't have that + many machines available on your network, you could start up + several nodes on the same machine.</p> + <p>We start up four Erlang nodes, messenger@super, c1@bilbo, + c2@kosken, c3@gollum.</p> + <p>First we start up a the server at messenger@super:</p> + <pre> +(messenger@super)1> <input>messenger:start_server().</input> +true</pre> + <p>Now Peter logs on at c1@bilbo:</p> + <pre> +(c1@bilbo)1> <input>messenger:logon(peter).</input> +true +logged_on</pre> + <p>James logs on at c2@kosken:</p> + <pre> +(c2@kosken)1> <input>messenger:logon(james).</input> +true +logged_on</pre> + <p>and Fred logs on at c3@gollum:</p> + <pre> +(c3@gollum)1> <input>messenger:logon(fred).</input> +true +logged_on</pre> + <p>Now Peter sends Fred a message:</p> + <pre> +(c1@bilbo)2> <input>messenger:message(fred, "hello").</input> +ok +sent</pre> + <p>And Fred receives the message and sends a message to Peter and + logs off:</p> + <pre> +Message from peter: "hello" +(c3@gollum)2> <input>messenger:message(peter, "go away, I'm busy").</input> +ok +sent +(c3@gollum)3> <input>messenger:logoff().</input> +logoff</pre> + <p>James now tries to send a message to Fred:</p> + <pre> +(c2@kosken)2> <input>messenger:message(fred, "peter doesn't like you").</input> +ok +receiver_not_found</pre> + <p>But this fails as Fred has already logged off.</p> + <p>First let's look at some of the new concepts we have introduced.</p> + <p>There are two versions of the <c>server_transfer</c> function, + one with four arguments (<c>server_transfer/4</c>) and one with + five (<c>server_transfer/5</c>). These are regarded by Erlang as + two separate functions.</p> + <p>Note how we write the <c>server</c> function so that it calls + itself, <c>server(User_List)</c> and thus creates a loop. + The Erlang compiler is "clever" and optimizes the code so that + this really is a sort of loop and not a proper function call. But + this only works if there is no code after the call, otherwise + the compiler will expect the call to return and make a proper + function call. This would result in the process getting bigger + and bigger for every loop.</p> + <p>We use functions in the <c>lists</c> module. This is a very + useful module and a study of the manual page is recommended + (<c>erl -man lists</c>). + <c>lists:keymember(Key,Position,Lists)</c> looks through a list + of tuples and looks at <c>Position</c> in each tuple to see if it + is the same as <c>Key</c>. The first element is position 1. If it + finds a tuple where the element at <c>Position</c> is the same as + Key, it returns <c>true</c>, otherwise <c>false</c>.</p> + <pre> +3> <input>lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).</input> +true +4> <input>lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).</input> +false</pre> + <p><c>lists:keydelete</c> works in the same way but deletes + the first tuple found (if any) and returns the remaining list:</p> + <pre> +5> <input>lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).</input> +[{x,y,z},{b,b,b},{q,r,s}]</pre> + <p><c>lists:keysearch</c> is like <c>lists:keymember</c>, but it + returns <c>{value,Tuple_Found}</c> or the atom <c>false</c>.</p> + <p>There are a lot more very useful functions in the <c>lists</c> + module.</p> + <p>An Erlang process will (conceptually) run until it does a + <c>receive</c> and there is no message which it wants to receive + in the message queue. I say "conceptually" because the Erlang + system shares the CPU time between the active processes in + the system.</p> + <p>A process terminates when there is nothing more for it to do, + i.e. the last function it calls simply returns and doesn't call + another function. Another way for a process to terminate is for + it to call <c>exit/1</c>. The argument to <c>exit/1</c> has a + special meaning which we will look at later. In this example we + will do <c>exit(normal)</c> which has the same effect as a + process running out of functions to call.</p> + <p>The BIF <c>whereis(RegisteredName)</c> checks if a registered + process of name <c>RegisteredName</c> exists and return the pid + of the process if it does exist or the atom <c>undefined</c> if + it does not.</p> + <p>You should by now be able to understand most of the code above + so I'll just go through one case: a message is sent from one user + to another.</p> + <p>The first user "sends" the message in the example above by:</p> + <code type="none"> +messenger:message(fred, "hello")</code> + <p>After testing that the client process exists:</p> + <code type="none"> +whereis(mess_client) </code> + <p>and a message is sent to <c>mess_client</c>:</p> + <code type="none"> +mess_client ! {message_to, fred, "hello"}</code> + <p>The client sends the message to the server by:</p> + <code type="none"> +{messenger, messenger@super} ! {self(), message_to, fred, "hello"},</code> + <p>and waits for a reply from the server.</p> + <p>The server receives this message and calls:</p> + <code type="none"> +server_transfer(From, fred, "hello", User_List),</code> + <p>which checks that the pid <c>From</c> is in the <c>User_List</c>:</p> + <code type="none"> +lists:keysearch(From, 1, User_List) </code> + <p>If <c>keysearch</c> returns the atom <c>false</c>, some sort of + error has occurred and the server sends back the message:</p> + <code type="none"> +From ! {messenger, stop, you_are_not_logged_on}</code> + <p>which is received by the client which in turn does + <c>exit(normal)</c> and terminates. If <c>keysearch</c> returns + <c>{value,{From,Name}}</c> we know that the user is logged on and + is his name (peter) is in variable <c>Name</c>. We now call:</p> + <code type="none"> +server_transfer(From, peter, fred, "hello", User_List)</code> + <p>Note that as this is <c>server_transfer/5</c> it is not the same + as the previous function <c>server_transfer/4</c>. We do another + <c>keysearch</c> on <c>User_List</c> to find the pid of the client + corresponding to fred:</p> + <code type="none"> +lists:keysearch(fred, 2, User_List)</code> + <p>This time we use argument 2 which is the second element in + the tuple. If this returns the atom <c>false</c> we know that + fred is not logged on and we send the message:</p> + <code type="none"> +From ! {messenger, receiver_not_found};</code> + <p>which is received by the client, if <c>keysearch</c> returns:</p> + <code type="none"> +{value, {ToPid, fred}}</code> + <p>we send the message:</p> + <code type="none"> +ToPid ! {message_from, peter, "hello"}, </code> + <p>to fred's client and the message:</p> + <code type="none"> +From ! {messenger, sent} </code> + <p>to peter's client.</p> + <p>Fred's client receives the message and prints it:</p> + <code type="none"> +{message_from, peter, "hello"} -> + io:format("Message from ~p: ~p~n", [peter, "hello"])</code> + <p>and peter's client receives the message in + the <c>await_result</c> function.</p> + </section> +</chapter> + diff --git a/system/doc/getting_started/intro.xml b/system/doc/getting_started/intro.xml new file mode 100644 index 0000000000..7b42080723 --- /dev/null +++ b/system/doc/getting_started/intro.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2003</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>Introduction</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>intro.xml</file> + </header> + + <section> + <title>Introduction</title> + <p>This is a "kick start" tutorial to get you started with Erlang. + Everything here is true, but only part of the truth. For example, + I'll only tell you the simplest form of the syntax, not all + esoteric forms. Where I've greatly oversimplified things I'll + write *manual* which means there is lots more information to be + found in the Erlang book or in the <em>Erlang Reference Manual</em>.</p> + <p>I also assume that this isn't the first time you have touched a + computer and you have a basic idea about how they are programmed. + Don't worry, I won't assume you're a wizard programmer.</p> + </section> + + <section> + <title>Things Left Out</title> + <p>In particular the following has been omitted:</p> + <list type="bulleted"> + <item>References</item> + <item>Local error handling (catch/throw)</item> + <item>Single direction links (monitor)</item> + <item>Handling of binary data (binaries / bit syntax)</item> + <item>List comprehensions</item> + <item>How to communicate with the outside world and/or software + written in other languages (ports). There is however a separate + tutorial for this, <em>Interoperability Tutorial</em></item> + <item>Very few of the Erlang libraries have been touched on (for + example file handling)</item> + <item>OTP has been totally skipped and in consequence the Mnesia + database has been skipped.</item> + <item>Hash tables for Erlang terms (ETS)</item> + <item>Changing code in running systems</item> + </list> + </section> +</chapter> + diff --git a/system/doc/getting_started/make.dep b/system/doc/getting_started/make.dep new file mode 100644 index 0000000000..69b177f77c --- /dev/null +++ b/system/doc/getting_started/make.dep @@ -0,0 +1,14 @@ +# ---------------------------------------------------- +# >>>> Do not edit this file <<<< +# This file was automaticly generated by +# /home/otp/bin/docdepend +# ---------------------------------------------------- + + +# ---------------------------------------------------- +# TeX files that the DVI file depend on +# ---------------------------------------------------- + +book.dvi: book.tex conc_prog.tex intro.tex part.tex \ + records_macros.tex robustness.tex seq_prog.tex + diff --git a/system/doc/getting_started/part.xml b/system/doc/getting_started/part.xml new file mode 100644 index 0000000000..4c277419a4 --- /dev/null +++ b/system/doc/getting_started/part.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE part SYSTEM "part.dtd"> + +<part xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>1997</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>Getting Started With Erlang</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + </header> + <xi:include href="intro.xml"/> + <xi:include href="seq_prog.xml"/> + <xi:include href="conc_prog.xml"/> + <xi:include href="robustness.xml"/> + <xi:include href="records_macros.xml"/> +</part> + diff --git a/system/doc/getting_started/records_macros.xml b/system/doc/getting_started/records_macros.xml new file mode 100644 index 0000000000..45617f0183 --- /dev/null +++ b/system/doc/getting_started/records_macros.xml @@ -0,0 +1,328 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2003</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>Records and Macros</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>record_macros.xml</file> + </header> + <p>Larger programs are usually written as a collection of files with + a well defined interface between the various parts.</p> + + <section> + <title>The Larger Example Divided into Several Files</title> + <p>To illustrate this, we will divide the messenger example from + the previous chapter into five files.</p> + <taglist> + <tag><c>mess_config.hrl</c></tag> + <item>header file for configuration data</item> + <tag><c>mess_interface.hrl</c></tag> + <item>interface definitions between the client and the messenger</item> + <tag><c>user_interface.erl</c></tag> + <item>functions for the user interface</item> + <tag><c>mess_client.erl</c></tag> + <item>functions for the client side of the messenger</item> + <tag><c>mess_server.erl</c></tag> + <item>functions for the server side of the messenger</item> + </taglist> + <p>While doing this we will also clean up the message passing + interface between the shell, the client and the server and define + it using <em>records</em>, we will also introduce <em>macros</em>.</p> + <code type="none"> +%%%----FILE mess_config.hrl---- + +%%% Configure the location of the server node, +-define(server_node, messenger@super). + +%%%----END FILE----</code> + <code type="none"> +%%%----FILE mess_interface.hrl---- + +%%% Message interface between client and server and client shell for +%%% messenger program + +%%%Messages from Client to server received in server/1 function. +-record(logon,{client_pid, username}). +-record(message,{client_pid, to_name, message}). +%%% {'EXIT', ClientPid, Reason} (client terminated or unreachable. + +%%% Messages from Server to Client, received in await_result/0 function +-record(abort_client,{message}). +%%% Messages are: user_exists_at_other_node, +%%% you_are_not_logged_on +-record(server_reply,{message}). +%%% Messages are: logged_on +%%% receiver_not_found +%%% sent (Message has been sent (no guarantee) +%%% Messages from Server to Client received in client/1 function +-record(message_from,{from_name, message}). + +%%% Messages from shell to Client received in client/1 function +%%% spawn(mess_client, client, [server_node(), Name]) +-record(message_to,{to_name, message}). +%%% logoff + +%%%----END FILE----</code> + <code type="none"> +%%%----FILE user_interface.erl---- + +%%% User interface to the messenger program +%%% login(Name) +%%% One user at a time can log in from each Erlang node in the +%%% system messenger: and choose a suitable Name. If the Name +%%% is already logged in at another node or if someone else is +%%% already logged in at the same node, login will be rejected +%%% with a suitable error message. + +%%% logoff() +%%% Logs off anybody at at node + +%%% message(ToName, Message) +%%% sends Message to ToName. Error messages if the user of this +%%% function is not logged on or if ToName is not logged on at +%%% any node. + +-module(user_interface). +-export([logon/1, logoff/0, message/2]). +-include("mess_interface.hrl"). +-include("mess_config.hrl"). + +logon(Name) -> + case whereis(mess_client) of + undefined -> + register(mess_client, + spawn(mess_client, client, [?server_node, Name])); + _ -> already_logged_on + end. + +logoff() -> + mess_client ! logoff. + +message(ToName, Message) -> + case whereis(mess_client) of % Test if the client is running + undefined -> + not_logged_on; + _ -> mess_client ! #message_to{to_name=ToName, message=Message}, + ok +end. + +%%%----END FILE----</code> + <code type="none"> +%%%----FILE mess_client.erl---- + +%%% The client process which runs on each user node + +-module(mess_client). +-export([client/2]). +-include("mess_interface.hrl"). + +client(Server_Node, Name) -> + {messenger, Server_Node} ! #logon{client_pid=self(), username=Name}, + await_result(), + client(Server_Node). + +client(Server_Node) -> + receive + logoff -> + exit(normal); + #message_to{to_name=ToName, message=Message} -> + {messenger, Server_Node} ! + #message{client_pid=self(), to_name=ToName, message=Message}, + await_result(); + {message_from, FromName, Message} -> + io:format("Message from ~p: ~p~n", [FromName, Message]) + end, + client(Server_Node). + +%%% wait for a response from the server +await_result() -> + receive + #abort_client{message=Why} -> + io:format("~p~n", [Why]), + exit(normal); + #server_reply{message=What} -> + io:format("~p~n", [What]) + after 5000 -> + io:format("No response from server~n", []), + exit(timeout) + end. + +%%%----END FILE---</code> + <code type="none"> +%%%----FILE mess_server.erl---- + +%%% This is the server process of the messenger service + +-module(mess_server). +-export([start_server/0, server/0]). +-include("mess_interface.hrl"). + +server() -> + process_flag(trap_exit, true), + server([]). + +%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...] +server(User_List) -> + io:format("User list = ~p~n", [User_List]), + receive + #logon{client_pid=From, username=Name} -> + New_User_List = server_logon(From, Name, User_List), + server(New_User_List); + {'EXIT', From, _} -> + New_User_List = server_logoff(From, User_List), + server(New_User_List); + #message{client_pid=From, to_name=To, message=Message} -> + server_transfer(From, To, Message, User_List), + server(User_List) + end. + +%%% Start the server +start_server() -> + register(messenger, spawn(?MODULE, server, [])). + +%%% Server adds a new user to the user list +server_logon(From, Name, User_List) -> + %% check if logged on anywhere else + case lists:keymember(Name, 2, User_List) of + true -> + From ! #abort_client{message=user_exists_at_other_node}, + User_List; + false -> + From ! #server_reply{message=logged_on}, + link(From), + [{From, Name} | User_List] %add user to the list + end. + +%%% Server deletes a user from the user list +server_logoff(From, User_List) -> + lists:keydelete(From, 1, User_List). + +%%% Server transfers a message between user +server_transfer(From, To, Message, User_List) -> + %% check that the user is logged on and who he is + case lists:keysearch(From, 1, User_List) of + false -> + From ! #abort_client{message=you_are_not_logged_on}; + {value, {_, Name}} -> + server_transfer(From, Name, To, Message, User_List) + end. +%%% If the user exists, send the message +server_transfer(From, Name, To, Message, User_List) -> + %% Find the receiver and send the message + case lists:keysearch(To, 2, User_List) of + false -> + From ! #server_reply{message=receiver_not_found}; + {value, {ToPid, To}} -> + ToPid ! #message_from{from_name=Name, message=Message}, + From ! #server_reply{message=sent} + end. + +%%%----END FILE---</code> + </section> + + <section> + <title>Header Files</title> + <p>You will see some files above with extension <c>.hrl</c>. These + are header files which are included in the <c>.erl</c> files by:</p> + <code type="none"> +-include("File_Name").</code> + <p>for example:</p> + <code type="none"> +-include("mess_interface.hrl").</code> + <p>In our case above the file is fetched from the same directory as + all the other files in the messenger example. (*manual*).</p> + <p>.hrl files can contain any valid Erlang code but are most often + used for record and macro definitions.</p> + </section> + + <section> + <title>Records</title> + <p>A record is defined as:</p> + <code type="none"> +-record(name_of_record,{field_name1, field_name2, field_name3, ......}).</code> + <p>For example:</p> + <code type="none"> +-record(message_to,{to_name, message}).</code> + <p>This is exactly equivalent to:</p> + <code type="none"> +{message_to, To_Name, Message}</code> + <p>Creating record, is best illustrated by an example:</p> + <code type="none"> +#message_to{message="hello", to_name=fred)</code> + <p>This will create:</p> + <code type="none"> +{message_to, fred, "hello"}</code> + <p>Note that you don't have to worry about the order you assign + values to the various parts of the records when you create it. + The advantage of using records is that by placing their + definitions in header files you can conveniently define + interfaces which are easy to change. For example, if you want to + add a new field to the record, you will only have to change + the code where the new field is used and not at every place + the record is referred to. If you leave out a field when creating + a record, it will get the value of the atom undefined. (*manual*)</p> + <p>Pattern matching with records is very similar to creating + records. For example inside a <c>case</c> or <c>receive</c>:</p> + <code type="none"> +#message_to{to_name=ToName, message=Message} -></code> + <p>is the same as:</p> + <code type="none"> +{message_to, ToName, Message}</code> + </section> + + <section> + <title>Macros</title> + <p>The other thing we have added to the messenger is a macro. + The file <c>mess_config.hrl</c> contains the definition:</p> + <code type="none"> +%%% Configure the location of the server node, +-define(server_node, messenger@super).</code> + <p>We include this file in mess_server.erl:</p> + <code type="none"> +-include("mess_config.hrl").</code> + <p>Every occurrence of <c>?server_node</c> in <c>mess_server.erl</c> + will now be replaced by <c>messenger@super</c>.</p> + <p>The other place a macro is used is when we spawn the server + process:</p> + <code type="none"> +spawn(?MODULE, server, [])</code> + <p>This is a standard macro (i.e. defined by the system, not + the user). <c>?MODULE</c> is always replaced by the name of + current module (i.e. the <c>-module</c> definition near the start + of the file). There are more advanced ways of using macros with, + for example parameters (*manual*).</p> + <p>The three Erlang (<c>.erl</c>) files in the messenger example are + individually compiled into object code file (<c>.beam</c>). + The Erlang system loads and links these files into the system + when they are referred to during execution of the code. In our + case we simply have put them in the same directory which is our + current working directory (i.e. the place we have done "cd" to). + There are ways of putting the <c>.beam</c> files in other + directories.</p> + <p>In the messenger example, no assumptions have been made about + what the message being sent is. It could be any valid Erlang term.</p> + </section> +</chapter> + diff --git a/system/doc/getting_started/robustness.xml b/system/doc/getting_started/robustness.xml new file mode 100644 index 0000000000..227da4c027 --- /dev/null +++ b/system/doc/getting_started/robustness.xml @@ -0,0 +1,483 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2003</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>Robustness</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>robustness.xml</file> + </header> + <p>There are several things which are wrong with + the <seealso marker="conc_prog#ex">messenger example</seealso> from + the previous chapter. For example if a node where a user is logged + on goes down without doing a log off, the user will remain in + the server's <c>User_List</c> but the client will disappear thus + making it impossible for the user to log on again as the server + thinks the user already logged on.</p> + <p>Or what happens if the server goes down in the middle of sending a + message leaving the sending client hanging for ever in + the <c>await_result</c> function?</p> + + <section> + <title>Timeouts</title> + <p>Before improving the messenger program we will look into some + general principles, using the ping pong program as an example. + Recall that when "ping" finishes, it tells "pong" that it has + done so by sending the atom <c>finished</c> as a message to "pong" + so that "pong" could also finish. Another way to let "pong" + finish, is to make "pong" exit if it does not receive a message + from ping within a certain time, this can be done by adding a + <em>timeout</em> to <c>pong</c> as shown in the following example:</p> + <code type="none"> +-module(tut19). + +-export([start_ping/1, start_pong/0, ping/2, pong/0]). + +ping(0, Pong_Node) -> + io:format("ping finished~n", []); + +ping(N, Pong_Node) -> + {pong, Pong_Node} ! {ping, self()}, + receive + pong -> + io:format("Ping received pong~n", []) + end, + ping(N - 1, Pong_Node). + +pong() -> + receive + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong() + after 5000 -> + io:format("Pong timed out~n", []) + end. + +start_pong() -> + register(pong, spawn(tut19, pong, [])). + +start_ping(Pong_Node) -> + spawn(tut19, ping, [3, Pong_Node]).</code> + <p>After we have compiled this and copied the <c>tut19.beam</c> + file to the necessary directories:</p> + <p>On (pong@kosken):</p> + <pre> +(pong@kosken)1> <input>tut19:start_pong().</input> +true +Pong received ping +Pong received ping +Pong received ping +Pong timed out</pre> + <p>On (ping@gollum):</p> + <pre> +(ping@gollum)1> <input>tut19:start_ping(pong@kosken).</input> +<0.36.0> +Ping received pong +Ping received pong +Ping received pong +ping finished </pre> + <p>(The timeout is set in:</p> + <code type="none"> +pong() -> + receive + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong() + after 5000 -> + io:format("Pong timed out~n", []) + end.</code> + <p>We start the timeout (<c>after 5000</c>) when we enter + <c>receive</c>. The timeout is canceled if <c>{ping,Ping_PID}</c> + is received. If <c>{ping,Ping_PID}</c> is not received, + the actions following the timeout will be done after 5000 + milliseconds. <c>after</c> must be last in the <c>receive</c>, + i.e. preceded by all other message reception specifications in + the <c>receive</c>. Of course we could also call a function which + returned an integer for the timeout:</p> + <code type="none"> +after pong_timeout() -></code> + <p>In general, there are better ways than using timeouts to + supervise parts of a distributed Erlang system. Timeouts are + usually appropriate to supervise external events, for example if + you have expected a message from some external system within a + specified time. For example, we could use a timeout to log a user + out of the messenger system if they have not accessed it, for + example, in ten minutes.</p> + </section> + + <section> + <title>Error Handling</title> + <p>Before we go into details of the supervision and error handling + in an Erlang system, we need see how Erlang processes terminate, + or in Erlang terminology, <em>exit</em>.</p> + <p>A process which executes <c>exit(normal)</c> or simply runs out + of things to do has a <em>normal</em> exit.</p> + <p>A process which encounters a runtime error (e.g. divide by zero, + bad match, trying to call a function which doesn't exist etc) + exits with an error, i.e. has an <em>abnormal</em> exit. A + process which executes + <seealso marker="erts:erlang#exit/1">exit(Reason)</seealso> + where <c>Reason</c> is any Erlang term except the atom + <c>normal</c>, also has an abnormal exit.</p> + <p>An Erlang process can set up links to other Erlang processes. If + a process calls + <seealso marker="erts:erlang#link/1">link(Other_Pid)</seealso> + it sets up a bidirectional link between itself and the process + called <c>Other_Pid</c>. When a process terminates its sends + something called a <em>signal</em> to all the processes it has + links to.</p> + <p>The signal carries information about the pid it was sent from and + the exit reason.</p> + <p>The default behaviour of a process which receives a normal exit + is to ignore the signal.</p> + <p>The default behaviour in the two other cases (i.e. abnormal exit) + above is to bypass all messages to the receiving process and to + kill it and to propagate the same error signal to the killed + process' links. In this way you can connect all processes in a + transaction together using links and if one of the processes + exits abnormally, all the processes in the transaction will be + killed. As we often want to create a process and link to it at + the same time, there is a special BIF, + <seealso marker="erts:erlang#spawn_link/1">spawn_link</seealso> + which does the same as <c>spawn</c>, but also creates a link to + the spawned process.</p> + <p>Now an example of the ping pong example using links to terminate + "pong":</p> + <code type="none"> +-module(tut20). + +-export([start/1, ping/2, pong/0]). + +ping(N, Pong_Pid) -> + link(Pong_Pid), + ping1(N, Pong_Pid). + +ping1(0, _) -> + exit(ping); + +ping1(N, Pong_Pid) -> + Pong_Pid ! {ping, self()}, + receive + pong -> + io:format("Ping received pong~n", []) + end, + ping1(N - 1, Pong_Pid). + +pong() -> + receive + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong() + end. + +start(Ping_Node) -> + PongPID = spawn(tut20, pong, []), + spawn(Ping_Node, tut20, ping, [3, PongPID]).</code> + <pre> +(s1@bill)3> <input>tut20:start(s2@kosken).</input> +Pong received ping +<3820.41.0> +Ping received pong +Pong received ping +Ping received pong +Pong received ping +Ping received pong</pre> + <p>This is a slight modification of the ping pong program where both + processes are spawned from the same <c>start/1</c> function, + where the "ping" process can be spawned on a separate node. Note + the use of the <c>link</c> BIF. "Ping" calls + <c>exit(ping)</c> when it finishes and this will cause an exit + signal to be sent to "pong" which will also terminate.</p> + <p>It is possible to modify the default behaviour of a process so + that it does not get killed when it receives abnormal exit + signals, but all signals will be turned into normal messages on + the format <c>{'EXIT',FromPID,Reason}</c> and added to the end of + the receiving processes message queue. This behaviour is set by:</p> + <code type="none"> +process_flag(trap_exit, true)</code> + <p>There are several other process flags, see + <seealso marker="erts:erlang#process_flag/2">erlang(3)</seealso>. + Changing the default behaviour of a process in this way is + usually not done in standard user programs, but is left to + the supervisory programs in OTP (but that's another tutorial). + However we will modify the ping pong program to illustrate exit + trapping.</p> + <code type="none"> +-module(tut21). + +-export([start/1, ping/2, pong/0]). + +ping(N, Pong_Pid) -> + link(Pong_Pid), + ping1(N, Pong_Pid). + +ping1(0, _) -> + exit(ping); + +ping1(N, Pong_Pid) -> + Pong_Pid ! {ping, self()}, + receive + pong -> + io:format("Ping received pong~n", []) + end, + ping1(N - 1, Pong_Pid). + +pong() -> + process_flag(trap_exit, true), + pong1(). + +pong1() -> + receive + {ping, Ping_PID} -> + io:format("Pong received ping~n", []), + Ping_PID ! pong, + pong1(); + {'EXIT', From, Reason} -> + io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}]) + end. + +start(Ping_Node) -> + PongPID = spawn(tut21, pong, []), + spawn(Ping_Node, tut21, ping, [3, PongPID]).</code> + <pre> +(s1@bill)1> <input>tut21:start(s2@gollum).</input> +<3820.39.0> +Pong received ping +Ping received pong +Pong received ping +Ping received pong +Pong received ping +Ping received pong +pong exiting, got {'EXIT',<3820.39.0>,ping}</pre> + </section> + + <section> + <title>The Larger Example with Robustness Added</title> + <p>Now we return to the messenger program and add changes which + make it more robust:</p> + <code type="none"> +%%% Message passing utility. +%%% User interface: +%%% login(Name) +%%% One user at a time can log in from each Erlang node in the +%%% system messenger: and choose a suitable Name. If the Name +%%% is already logged in at another node or if someone else is +%%% already logged in at the same node, login will be rejected +%%% with a suitable error message. +%%% logoff() +%%% Logs off anybody at at node +%%% message(ToName, Message) +%%% sends Message to ToName. Error messages if the user of this +%%% function is not logged on or if ToName is not logged on at +%%% any node. +%%% +%%% One node in the network of Erlang nodes runs a server which maintains +%%% data about the logged on users. The server is registered as "messenger" +%%% Each node where there is a user logged on runs a client process registered +%%% as "mess_client" +%%% +%%% Protocol between the client processes and the server +%%% ---------------------------------------------------- +%%% +%%% To server: {ClientPid, logon, UserName} +%%% Reply {messenger, stop, user_exists_at_other_node} stops the client +%%% Reply {messenger, logged_on} logon was successful +%%% +%%% When the client terminates for some reason +%%% To server: {'EXIT', ClientPid, Reason} +%%% +%%% To server: {ClientPid, message_to, ToName, Message} send a message +%%% Reply: {messenger, stop, you_are_not_logged_on} stops the client +%%% Reply: {messenger, receiver_not_found} no user with this name logged on +%%% Reply: {messenger, sent} Message has been sent (but no guarantee) +%%% +%%% To client: {message_from, Name, Message}, +%%% +%%% Protocol between the "commands" and the client +%%% ---------------------------------------------- +%%% +%%% Started: messenger:client(Server_Node, Name) +%%% To client: logoff +%%% To client: {message_to, ToName, Message} +%%% +%%% Configuration: change the server_node() function to return the +%%% name of the node where the messenger server runs + +-module(messenger). +-export([start_server/0, server/0, + logon/1, logoff/0, message/2, client/2]). + +%%% Change the function below to return the name of the node where the +%%% messenger server runs +server_node() -> + messenger@super. + +%%% This is the server process for the "messenger" +%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...] +server() -> + process_flag(trap_exit, true), + server([]). + +server(User_List) -> + receive + {From, logon, Name} -> + New_User_List = server_logon(From, Name, User_List), + server(New_User_List); + {'EXIT', From, _} -> + New_User_List = server_logoff(From, User_List), + server(New_User_List); + {From, message_to, To, Message} -> + server_transfer(From, To, Message, User_List), + io:format("list is now: ~p~n", [User_List]), + server(User_List) + end. + +%%% Start the server +start_server() -> + register(messenger, spawn(messenger, server, [])). + +%%% Server adds a new user to the user list +server_logon(From, Name, User_List) -> + %% check if logged on anywhere else + case lists:keymember(Name, 2, User_List) of + true -> + From ! {messenger, stop, user_exists_at_other_node}, %reject logon + User_List; + false -> + From ! {messenger, logged_on}, + link(From), + [{From, Name} | User_List] %add user to the list + end. + +%%% Server deletes a user from the user list +server_logoff(From, User_List) -> + lists:keydelete(From, 1, User_List). + + +%%% Server transfers a message between user +server_transfer(From, To, Message, User_List) -> + %% check that the user is logged on and who he is + case lists:keysearch(From, 1, User_List) of + false -> + From ! {messenger, stop, you_are_not_logged_on}; + {value, {_, Name}} -> + server_transfer(From, Name, To, Message, User_List) + end. + +%%% If the user exists, send the message +server_transfer(From, Name, To, Message, User_List) -> + %% Find the receiver and send the message + case lists:keysearch(To, 2, User_List) of + false -> + From ! {messenger, receiver_not_found}; + {value, {ToPid, To}} -> + ToPid ! {message_from, Name, Message}, + From ! {messenger, sent} + end. + +%%% User Commands +logon(Name) -> + case whereis(mess_client) of + undefined -> + register(mess_client, + spawn(messenger, client, [server_node(), Name])); + _ -> already_logged_on + end. + +logoff() -> + mess_client ! logoff. + +message(ToName, Message) -> + case whereis(mess_client) of % Test if the client is running + undefined -> + not_logged_on; + _ -> mess_client ! {message_to, ToName, Message}, + ok +end. + +%%% The client process which runs on each user node +client(Server_Node, Name) -> + {messenger, Server_Node} ! {self(), logon, Name}, + await_result(), + client(Server_Node). + +client(Server_Node) -> + receive + logoff -> + exit(normal); + {message_to, ToName, Message} -> + {messenger, Server_Node} ! {self(), message_to, ToName, Message}, + await_result(); + {message_from, FromName, Message} -> + io:format("Message from ~p: ~p~n", [FromName, Message]) + end, + client(Server_Node). + +%%% wait for a response from the server +await_result() -> + receive + {messenger, stop, Why} -> % Stop the client + io:format("~p~n", [Why]), + exit(normal); + {messenger, What} -> % Normal response + io:format("~p~n", [What]) + after 5000 -> + io:format("No response from server~n", []), + exit(timeout) + end.</code> + <p>We have added the following changes:</p> + <p>The messenger server traps exits. If it receives an exit signal, + <c>{'EXIT',From,Reason}</c> this means that a client process has + terminated or is unreachable because:</p> + <list type="bulleted"> + <item>the user has logged off (we have removed the "logoff" + message),</item> + <item>the network connection to the client is broken,</item> + <item>the node on which the client process resides has gone down, + or</item> + <item>the client processes has done some illegal operation.</item> + </list> + <p>If we receive an exit signal as above, we delete the tuple, + <c>{From,Name}</c> from the servers <c>User_List</c> using + the <c>server_logoff</c> function. If the node on which the server + runs goes down, an exit signal (automatically generated by + the system), will be sent to all of the client processes: + <c>{'EXIT',MessengerPID,noconnection}</c> causing all the client + processes to terminate.</p> + <p>We have also introduced a timeout of five seconds in + the <c>await_result</c> function. I.e. if the server does not + reply within five seconds (5000 ms), the client terminates. This + is really only needed in the logon sequence before the client and + server are linked.</p> + <p>An interesting case is if the client was to terminate before + the server links to it. This is taken care of because linking to a + non-existent process causes an exit signal, + <c>{'EXIT',From,noproc}</c>, to be automatically generated as if + the process terminated immediately after the link operation.</p> + </section> +</chapter> + diff --git a/system/doc/getting_started/seq_prog.xml b/system/doc/getting_started/seq_prog.xml new file mode 100644 index 0000000000..bc1758d855 --- /dev/null +++ b/system/doc/getting_started/seq_prog.xml @@ -0,0 +1,1231 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2003</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>Sequential Programming</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>seq_prog.xml</file> + </header> + + <section> + <title>The Erlang Shell</title> + <p>Most operating systems have a command interpreter or shell, Unix + and Linux have many, Windows has the Command Prompt. Erlang has + its own shell where you can directly write bits of Erlang code + and evaluate (run) them to see what happens (see + <seealso marker="stdlib:shell">shell(3)</seealso>). Start + the Erlang shell (in Linux or UNIX) by starting a shell or + command interpreter in your operating system and typing + <c>erl</c>, you will see something like this.</p> + <pre> +% <input>erl</input> +Erlang (BEAM) emulator version 5.2 [source] [hipe] + +Eshell V5.2 (abort with ^G) +1></pre> + <p>Now type in "2 + 5." as shown below.</p> + <pre> +1> <input>2 + 5.</input> +7 +2></pre> + <p>In Windows, the shell is started by double-clicking on the Erlang + shell icon.</p> + <p>You'll notice that the Erlang shell has numbered the lines that + can be entered, (as 1> 2>) and that it has correctly told you + that 2 + 5 is 7! Also notice that you have to tell it you are + done entering code by finishing with a full stop "." and a + carriage return. If you make mistakes writing things in the shell, + you can delete things by using the backspace key as in most + shells. There are many more editing commands in the shell + (See the chapter <seealso marker="erts:tty">"tty - A command line interface"</seealso> in ERTS User's Guide).</p> + <p>(Note: you will find a lot of line numbers given by the shell + out of sequence in this tutorial as it was written and the code + tested in several sessions).</p> + <p>Now let's try a more complex calculation.</p> + <pre> +2> <input>(42 + 77) * 66 / 3.</input> +2618.0</pre> + <p>Here you can see the use of brackets and the multiplication + operator "*" and division operator "/", just as in normal + arithmetic (see the chapter + <seealso marker="doc/reference_manual:expressions">"Arithmetic Expressions"</seealso> in the Erlang Reference Manual).</p> + <p>To shutdown the Erlang system and the Erlang shell type + Control-C. You will see the following output:</p> + <pre> +BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded + (v)ersion (k)ill (D)b-tables (d)istribution +<input>a</input> +%</pre> + <p>Type "a" to leave the Erlang system.</p> + <p>Another way to shutdown the Erlang system is by entering + <c>halt()</c>:</p> + <pre> +3> <input>halt().</input> +% </pre> + </section> + + <section> + <title>Modules and Functions</title> + <p>A programming language isn't much use if you can just run code + from the shell. So here is a small Erlang program. Enter it into + a file called <c>tut.erl</c> (the file name <c>tut.erl</c> is + important, also make sure that it is in the same directory as + the one where you started <c>erl</c>) using a suitable + text editor. If you are lucky your editor will have an Erlang + mode which will make it easier for you to enter and format your + code nicely (see the chapter + <seealso marker="tools:erlang_mode_chapter">"The Erlang mode for Emacs"</seealso> in Tools User's Guide), but you can manage + perfectly well without. Here's the code to enter:</p> + <code type="none"> +-module(tut). +-export([double/1]). + +double(X) -> + 2 * X.</code> + <p>It's not hard to guess that this "program" doubles the value of + numbers. I'll get back to the first two lines later. Let's compile + the program. This can be done in your Erlang shell as shown below:</p> + <pre> +3> <input>c(tut).</input> +{ok,tut}</pre> + <p>The <c>{ok,tut}</c> tells you that the compilation was OK. If it + said "error" instead, you have made some mistake in the text you + entered and there will also be error messages to give you some + idea as to what has gone wrong so you can change what you have + written and try again.</p> + <p>Now lets run the program.</p> + <pre> +4> <input>tut:double(10).</input> +20</pre> + <p>As expected double of 10 is 20.</p> + <p>Now let's get back to the first two lines. Erlang programs are + written in files. Each file contains what we call an Erlang + <em>module</em>. The first line of code in the module tells us + the name of the module (see the chapter + <seealso marker="doc/reference_manual:modules">"Modules"</seealso> + in the Erlang Reference Manual).</p> + <code type="none"> +-module(tut).</code> + <p>This tells us that the module is called <em>tut</em>. Note + the "." at the end of the line. The files which are used to store + the module must have the same name as the module but with + the extension ".erl". In our case the file name is <c>tut.erl</c>. + When we use a function in another module, we use the syntax, + <c>module_name:function_name(arguments)</c>. So</p> + <pre> +4> <input>tut:double(10).</input></pre> + <p>means call function <c>double</c> in module <c>tut</c> with + argument "10".</p> + <p>The second line:</p> + <code type="none"> +-export([double/1]).</code> + <p>says that the module <c>tut</c> contains a function called + <c>double</c> which takes one argument (<c>X</c> in our example) + and that this function can be called from outside the module + <c>tut</c>. More about this later. Again note the "." at the end + of the line.</p> + <p>Now for a more complicated example, the factorial of a number + (e.g. factorial of 4 is 4 * 3 * 2 * 1). Enter the following code + in a file called <c>tut1.erl</c>.</p> + <code type="none"> +-module(tut1). +-export([fac/1]). + +fac(1) -> + 1; +fac(N) -> + N * fac(N - 1).</code> + <p>Compile the file</p> + <pre> +5> <input>c(tut1).</input> +{ok,tut1}</pre> + <p>And now calculate the factorial of 4.</p> + <pre> +6> <input>tut1:fac(4).</input> +24</pre> + <p>The first part:</p> + <code type="none"> +fac(1) -> + 1;</code> + <p>says that the factorial of 1 is 1. Note that we end this part + with a ";" which indicates that there is more of this function to + come. The second part:</p> + <code type="none"> +fac(N) -> + N * fac(N - 1).</code> + <p>says that the factorial of N is N multiplied by the factorial of + N - 1. Note that this part ends with a "." saying that there are + no more parts of this function.</p> + <p>A function can have many arguments. Let's expand the module + <c>tut1</c> with the rather stupid function to multiply two + numbers:</p> + <code type="none"> +-module(tut1). +-export([fac/1, mult/2]). + +fac(1) -> + 1; +fac(N) -> + N * fac(N - 1). + +mult(X, Y) -> + X * Y.</code> + <p>Note that we have also had to expand the <c>-export</c> line + with the information that there is another function <c>mult</c> + with two arguments.</p> + <p>Compile:</p> + <pre> +7> <input>c(tut1).</input> +{ok,tut1}</pre> + <p>and try it out:</p> + <pre> +8> <input>tut1:mult(3,4).</input> +12</pre> + <p>In the example above the numbers are integers and the arguments + in the functions in the code, <c>N</c>, <c>X</c>, <c>Y</c> are + called variables. Variables must start with a capital letter + (see the chapter + <seealso marker="doc/reference_manual:expressions">"Variables"</seealso> + in the Erlang Reference Manual). Examples of variable could be + <c>Number</c>, <c>ShoeSize</c>, <c>Age</c> etc.</p> + </section> + + <section> + <title>Atoms</title> + <p>Atoms are another data type in Erlang. Atoms start with a small + letter ((see the chapter + <seealso marker="doc/reference_manual:data_types">"Atom"</seealso> + in the Erlang Reference Manual)), for example: <c>charles</c>, + <c>centimeter</c>, <c>inch</c>. Atoms are simply names, nothing + else. They are not like variables which can have a value.</p> + <p>Enter the next program (file: <c>tut2.erl</c>) which could be + useful for converting from inches to centimeters and vice versa:</p> + <code type="none"> +-module(tut2). +-export([convert/2]). + +convert(M, inch) -> + M / 2.54; + +convert(N, centimeter) -> + N * 2.54.</code> + <p>Compile and test:</p> + <pre> +9> <input>c(tut2).</input> +{ok,tut2} +10> <input>tut2:convert(3, inch).</input> +1.1811023622047243 +11> <input>tut2:convert(7, centimeter).</input> +17.78</pre> + <p>Notice that I have introduced decimals (floating point numbers) + without any explanation, but I guess you can cope with that.</p> + <p>See what happens if I enter something other than centimeter or + inch in the convert function:</p> + <pre> +12> <input>tut2:convert(3, miles).</input> +** exception error: no function clause matching tut2:convert(3,miles)</pre> + <p>The two parts of the <c>convert</c> function are called its + clauses. Here we see that "miles" is not part of either of + the clauses. The Erlang system can't <em>match</em> either of + the clauses so we get an error message <c>function_clause</c>. + The shell formats the error message nicely, but the error tuple + is saved in the shell's history list and can be output by the shell + command <c>v/1</c>:</p> + <pre> +13> <input>v(12).</input> +{'EXIT',{function_clause,[{tut2,convert,[3,miles]}, + {erl_eval,do_apply,5}, + {shell,exprs,6}, + {shell,eval_exprs,6}, + {shell,eval_loop,3}]}}</pre> + + </section> + + <section> + <title>Tuples</title> + <p>Now the <c>tut2</c> program is hardly good programming style. + Consider:</p> + <code type="none"> +tut2:convert(3, inch).</code> + <p>Does this mean that 3 is in inches? or that 3 is in centimeters + and we want to convert it to inches? So Erlang has a way to group + things together to make things more understandable. We call these + <em>tuples</em>. Tuples are surrounded by "{" and "}".</p> + <p>So we can write <c>{inch,3}</c> to denote 3 inches and + <c>{centimeter,5}</c> to denote 5 centimeters. Now let's write a + new program which converts centimeters to inches and vice versa. + (file <c>tut3.erl</c>).</p> + <code type="none"> +-module(tut3). +-export([convert_length/1]). + +convert_length({centimeter, X}) -> + {inch, X / 2.54}; +convert_length({inch, Y}) -> + {centimeter, Y * 2.54}.</code> + <p>Compile and test:</p> + <pre> +14> <input>c(tut3).</input> +{ok,tut3} +15> <input>tut3:convert_length({inch, 5}).</input> +{centimeter,12.7} +16> <input>tut3:convert_length(tut3:convert_length({inch, 5})).</input> +{inch,5.0}</pre> + <p>Note on line 16 we convert 5 inches to centimeters and back + again and reassuringly get back to the original value. I.e + the argument to a function can be the result of another function. + Pause for a moment and consider how line 16 (above) works. + The argument we have given the function <c>{inch,5}</c> is first + matched against the first head clause of <c>convert_length</c> + i.e. <c>convert_length({centimeter,X})</c> where it can be seen + that <c>{centimeter,X}</c> does not match <c>{inch,5}</c> + (the head is the bit before the "->"). This having failed, we try + the head of the next clause i.e. <c>convert_length({inch,Y})</c>, + this matches and <c>Y</c> get the value 5.</p> + <p>We have shown tuples with two parts above, but tuples can have + as many parts as we want and contain any valid Erlang + <em>term</em>. For example, to represent the temperature of + various cities of the world we could write</p> + <code type="none"> +{moscow, {c, -10}} +{cape_town, {f, 70}} +{paris, {f, 28}}</code> + <p>Tuples have a fixed number of things in them. We call each thing + in a tuple an element. So in the tuple <c>{moscow,{c,-10}}</c>, + element 1 is <c>moscow</c> and element 2 is <c>{c,-10}</c>. I + have chosen <c>c</c> meaning Centigrade (or Celsius) and <c>f</c> + meaning Fahrenheit.</p> + </section> + + <section> + <title>Lists</title> + <p>Whereas tuples group things together, we also want to be able to + represent lists of things. Lists in Erlang are surrounded by "[" + and "]". For example a list of the temperatures of various cities + in the world could be:</p> + <code type="none"> +[{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}}, + {paris, {f, 28}}, {london, {f, 36}}]</code> + <p>Note that this list was so long that it didn't fit on one line. + This doesn't matter, Erlang allows line breaks at all "sensible + places" but not, for example, in the middle of atoms, integers + etc.</p> + <p>A very useful way of looking at parts of lists, is by using "|". + This is best explained by an example using the shell.</p> + <pre> +17> <input>[First |TheRest] = [1,2,3,4,5].</input> +[1,2,3,4,5] +18> <input>First.</input> +1 +19> <input>TheRest.</input> +[2,3,4,5]</pre> + <p>We use | to separate the first elements of the list from + the rest of the list. (<c>First</c> has got value 1 and + <c>TheRest</c> value [2,3,4,5]).</p> + <p>Another example:</p> + <pre> +20> <input>[E1, E2 | R] = [1,2,3,4,5,6,7].</input> +[1,2,3,4,5,6,7] +21> <input>E1.</input> +1 +22> <input>E2.</input> +2 +23> <input>R.</input> +[3,4,5,6,7]</pre> + <p>Here we see the use of | to get the first two elements from + the list. Of course if we try to get more elements from the list + than there are elements in the list we will get an error. Note + also the special case of the list with no elements [].</p> + <pre> +24> <input>[A, B | C] = [1, 2].</input> +[1,2] +25> <input>A.</input> +1 +26> <input>B.</input> +2 +27> <input>C.</input> +[]</pre> + <p>In all the examples above, I have been using new variable names, + not reusing the old ones: <c>First</c>, <c>TheRest</c>, <c>E1</c>, + <c>E2</c>, <c>R</c>, <c>A</c>, <c>B</c>, <c>C</c>. The reason + for this is that a variable can only be given a value once in its + context (scope). I'll get back to this later, it isn't so + peculiar as it sounds!</p> + <p>The following example shows how we find the length of a list:</p> + <code type="none"> +-module(tut4). + +-export([list_length/1]). + +list_length([]) -> + 0; +list_length([First | Rest]) -> + 1 + list_length(Rest).</code> + <p>Compile (file <c>tut4.erl</c>) and test:</p> + <pre> +28> <input>c(tut4).</input> +{ok,tut4} +29> <input>tut4:list_length([1,2,3,4,5,6,7]).</input> +7</pre> + <p>Explanation:</p> + <code type="none"> +list_length([]) -> + 0;</code> + <p>The length of an empty list is obviously 0.</p> + <code type="none"> +list_length([First | Rest]) -> + 1 + list_length(Rest).</code> + <p>The length of a list with the first element <c>First</c> and + the remaining elements <c>Rest</c> is 1 + the length of + <c>Rest</c>.</p> + <p>(Advanced readers only: This is not tail recursive, there is a + better way to write this function).</p> + <p>In general we can say we use tuples where we would use "records" + or "structs" in other languages and we use lists when we want to + represent things which have varying sizes, (i.e. where we would + use linked lists in other languages).</p> + <p>Erlang does not have a string date type, instead strings can be + represented by lists of ASCII characters. So the list + <c>[97,98,99]</c> is equivalent to "abc". The Erlang shell is + "clever" and guesses the what sort of list we mean and outputs it + in what it thinks is the most appropriate form, for example:</p> + <pre> +30> <input>[97,98,99].</input> +"abc"</pre> + </section> + + <section> + <title>Standard Modules and Manual Pages</title> + <p>Erlang has a lot of standard modules to help you do things. For + example, the module <c>io</c> contains a lot of functions to help + you do formatted input/output. To look up information about + standard modules, the command <c>erl -man</c> can be used at + the operating shell or command prompt (i.e. at the same place as + that where you started <c>erl</c>). Try the operating system + shell command:</p> + <pre> +% <input>erl -man io</input> +ERLANG MODULE DEFINITION io(3) + +MODULE + io - Standard I/O Server Interface Functions + +DESCRIPTION + This module provides an interface to standard Erlang IO + servers. The output functions all return ok if they are suc- + ...</pre> + <p>If this doesn't work on your system, the documentation is + included as HTML in the Erlang/OTP release, or you can read + the documentation as HTML or download it as PDF from either of + the sites www.erlang.se (commercial Erlang) or www.erlang.org + (open source), for example for release R9B:</p> + <code type="none"> +http://www.erlang.org/doc/r9b/doc/index.html</code> + </section> + + <section> + <title>Writing Output to a Terminal</title> + <p>It's nice to be able to do formatted output in these example, so + the next example shows a simple way to use to use + the <c>io:format</c> function. Of course, just like all other + exported functions, you can test the <c>io:format</c> function in + the shell:</p> + <pre> +31> <input>io:format("hello world~n", []).</input> +hello world +ok +32> <input>io:format("this outputs one Erlang term: ~w~n", [hello]).</input> +this outputs one Erlang term: hello +ok +33> <input>io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).</input> +this outputs two Erlang terms: helloworld +ok +34> <input>io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).</input> +this outputs two Erlang terms: hello world +ok</pre> + <p>The function <c>format/2</c> (i.e. <c>format</c> with two + arguments) takes two lists. The first one is nearly always a list + written between " ". This list is printed out as it stands, + except that each ~w is replaced by a term taken in order from + the second list. Each ~n is replaced by a new line. + The <c>io:format/2</c> function itself returns the atom <c>ok</c> + if everything goes as planned. Like other functions in Erlang, it + crashes if an error occurs. This is not a fault in Erlang, it is + a deliberate policy. Erlang has sophisticated mechanisms to + handle errors which we will show later. As an exercise, try to + make <c>io:format</c> crash, it shouldn't be difficult. But + notice that although <c>io:format</c> crashes, the Erlang shell + itself does not crash.</p> + </section> + + <section> + <title>A Larger Example</title> + <p>Now for a larger example to consolidate what we have learnt so + far. Assume we have a list of temperature readings from a number + of cities in the world. Some of them are in Celsius (Centigrade) + and some in Fahrenheit (as in the previous list). First let's + convert them all to Celsius, then let's print out the data neatly.</p> + <code type="none"> +%% This module is in file tut5.erl + +-module(tut5). +-export([format_temps/1]). + +%% Only this function is exported +format_temps([])-> % No output for an empty list + ok; +format_temps([City | Rest]) -> + print_temp(convert_to_celsius(City)), + format_temps(Rest). + +convert_to_celsius({Name, {c, Temp}}) -> % No conversion needed + {Name, {c, Temp}}; +convert_to_celsius({Name, {f, Temp}}) -> % Do the conversion + {Name, {c, (Temp - 32) * 5 / 9}}. + +print_temp({Name, {c, Temp}}) -> + io:format("~-15w ~w c~n", [Name, Temp]).</code> + <pre> +35> <input>c(tut5).</input> +{ok,tut5} +36> <input>tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},</input> +<input>{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).</input> +moscow -10 c +cape_town 21.11111111111111 c +stockholm -4 c +paris -2.2222222222222223 c +london 2.2222222222222223 c +ok</pre> + <p>Before we look at how this program works, notice that we have + added a few comments to the code. A comment starts with a % + character and goes on to the end of the line. Note as well that + the <c>-export([format_temps/1]).</c> line only includes + the function <c>format_temps/1</c>, the other functions are + <em>local</em> functions, i.e. they are not visible from outside + the module <c>tut5</c>.</p> + <p>Note as well that when testing the program from the shell, I had + to spread the input over two lines as the line was too long.</p> + <p>When we call <c>format_temps</c> the first time, <c>City</c> + gets the value <c>{moscow,{c,-10}}</c> and <c>Rest</c> is + the rest of the list. So we call the function + <c>print_temp(convert_to_celsius({moscow,{c,-10}}))</c>.</p> + <p>Here we see a function call as + <c>convert_to_celsius({moscow,{c,-10}})</c> as the argument to + the function <c>print_temp</c>. When we <em>nest</em> function + calls like this we execute (evaluate) them from the inside out. + I.e. we first evaluate <c>convert_to_celsius({moscow,{c,-10}})</c> + which gives the value <c>{moscow,{c,-10}}</c> as the temperature + is already in Celsius and then we evaluate + <c>print_temp({moscow,{c,-10}})</c>. The function + <c>convert_to_celsius</c> works in a similar way to + the <c>convert_length</c> function in the previous example.</p> + <p><c>print_temp</c> simply calls <c>io:format</c> in a similar way + to what has been described above. Note that ~-15w says to print + the "term" with a field length (width) of 15 and left justify it. + (<seealso marker="stdlib:io#fwrite/1">io(3)</seealso>).</p> + <p>Now we call <c>format_temps(Rest)</c> with the rest of the list + as an argument. This way of doing things is similar to the loop + constructs in other languages. (Yes, this is recursion, but don't + let that worry you). So the same <c>format_temps</c> function is + called again, this time <c>City</c> gets the value + <c>{cape_town,{f,70}}</c> and we repeat the same procedure as + before. We go on doing this until the list becomes empty, i.e. [], + which causes the first clause <c>format_temps([])</c> to match. + This simply returns (results in) the atom <c>ok</c>, so + the program ends.</p> + </section> + + <section> + <title>Matching, Guards and Scope of Variables</title> + <p>It could be useful to find the maximum and minimum temperature + in lists like this. Before extending the program to do this, + let's look at functions for finding the maximum value of + the elements in a list:</p> + <code type="none"> +-module(tut6). +-export([list_max/1]). + +list_max([Head|Rest]) -> + list_max(Rest, Head). + +list_max([], Res) -> + Res; +list_max([Head|Rest], Result_so_far) when Head > Result_so_far -> + list_max(Rest, Head); +list_max([Head|Rest], Result_so_far) -> + list_max(Rest, Result_so_far).</code> + <pre> +37> <input>c(tut6).</input> +{ok,tut6} +38> <input>tut6:list_max([1,2,3,4,5,7,4,3,2,1]).</input> +7</pre> + <p>First note that we have two functions here with the same name + <c>list_max</c>. However each of these takes a different number + of arguments (parameters). In Erlang these are regarded as + completely different functions. Where we need to distinguish + between these functions we write <c>name/arity</c>, where + <c>name</c> is the name of the function and <c>arity</c> is + the number of arguments, in this case <c>list_max/1</c> and + <c>list_max/2</c>.</p> + <p>This is an example where we walk through a list "carrying" a + value with us, in this case <c>Result_so_far</c>. + <c>list_max/1</c> simply assumes that the max value of the list + is the head of the list and calls <c>list_max/2</c> with the rest + of the list and the value of the head of the list, in the above + this would be <c>list_max([2,3,4,5,7,4,3,2,1],1)</c>. If we tried + to use <c>list_max/1</c> with an empty list or tried to use it + with something which isn't a list at all, we would cause an error. + Note that the Erlang philosophy is not to handle errors of this + type in the function they occur, but to do so elsewhere. More + about this later.</p> + <p>In <c>list_max/2</c> we walk down the list and use <c>Head</c> + instead of <c>Result_so_far</c> when <c>Head</c> > + <c>Result_so_far</c>. <c>when</c> is a special word we use before + the -> in the function to say that we should only use this part + of the function if the test which follows is true. We call tests + of this type a <em>guard</em>. If the guard isn't true (we say + the guard fails), we try the next part of the function. In this + case if <c>Head</c> isn't greater than <c>Result_so_far</c> then + it must be smaller or equal to is, so we don't need a guard on + the next part of the function.</p> + <p>Some useful operators in guards are, < less than, > + greater than, == equal, >= greater or equal, =< less or + equal, /= not equal. (see the chapter + <seealso marker="doc/reference_manual:expressions">"Guard Sequences"</seealso> in the Erlang Reference Manual).</p> + <p>To change the above program to one which works out the minimum + value of the element in a list, all we would need to do is to + write < instead of >. (But it would be wise to change + the name of the function to <c>list_min</c> :-).</p> + <p>Remember that I mentioned earlier that a variable could only be + given a value once in its scope? In the above we see, for example, + that <c>Result_so_far</c> has been given several values. This is + OK since every time we call <c>list_max/2</c> we create a new + scope and one can regard the <c>Result_so_far</c> as a completely + different variable in each scope.</p> + <p>Another way of creating and giving a variable a value is by using + the match operator = . So if I write <c>M = 5</c>, a variable + called <c>M</c> will be created and given the value 5. If, in + the same scope I then write <c>M = 6</c>, I'll get an error. Try + this out in the shell:</p> + <pre> +39> <input>M = 5.</input> +5 +40> <input>M = 6.</input> +** exception error: no match of right hand side value 6 +41> <input>M = M + 1.</input> +** exception error: no match of right hand side value 6 +42> <input>N = M + 1.</input> +6</pre> + <p>The use of the match operator is particularly useful for pulling + apart Erlang terms and creating new ones.</p> + <pre> +43> <input>{X, Y} = {paris, {f, 28}}.</input> +{paris,{f,28}} +44> <input>X.</input> +paris +45> <input>Y.</input> +{f,28}</pre> + <p>Here we see that <c>X</c> gets the value <c>paris</c> and + <c>Y</c><c>{f,28}</c>.</p> + <p>Of course if we try to do the same again with another city, we + get an error:</p> + <pre> +46> <input>{X, Y} = {london, {f, 36}}.</input> +** exception error: no match of right hand side value {london,{f,36}}</pre> + <p>Variables can also be used to improve the readability of + programs, for example, in the <c>list_max/2</c> function above, + we could write:</p> + <code type="none"> +list_max([Head|Rest], Result_so_far) when Head > Result_so_far -> + New_result_far = Head, + list_max(Rest, New_result_far);</code> + <p>which is possibly a little clearer.</p> + </section> + + <section> + <title>More About Lists</title> + <p>Remember that the | operator can be used to get the head of a + list:</p> + <pre> +47> <input>[M1|T1] = [paris, london, rome].</input> +[paris,london,rome] +48> <input>M1.</input> +paris +49> <input>T1.</input> +[london,rome]</pre> + <p>The | operator can also be used to add a head to a list:</p> + <pre> +50> <input>L1 = [madrid | T1].</input> +[madrid,london,rome] +51> <input>L1.</input> +[madrid,london,rome]</pre> + <p>Now an example of this when working with lists - reversing + the order of a list:</p> + <code type="none"> +-module(tut8). + +-export([reverse/1]). + +reverse(List) -> + reverse(List, []). + +reverse([Head | Rest], Reversed_List) -> + reverse(Rest, [Head | Reversed_List]); +reverse([], Reversed_List) -> + Reversed_List.</code> + <pre> +52> <input>c(tut8).</input> +{ok,tut8} +53> <input>tut8:reverse([1,2,3]).</input> +[3,2,1]</pre> + <p>Consider how <c>Reversed_List</c> is built. It starts as [], we + then successively take off the heads of the list to be reversed + and add them to the the <c>Reversed_List</c>, as shown in + the following:</p> + <code type="none"> +reverse([1|2,3], []) => + reverse([2,3], [1|[]]) + +reverse([2|3], [1]) => + reverse([3], [2|[1]) + +reverse([3|[]], [2,1]) => + reverse([], [3|[2,1]]) + +reverse([], [3,2,1]) => + [3,2,1]</code> + <p>The module <c>lists</c> contains a lot of functions for + manipulating lists, for example for reversing them, so before you + write a list manipulating function it is a good idea to check + that one isn't already written for you. (see + <seealso marker="stdlib:lists">lists(3)</seealso>).</p> + <p>Now lets get back to the cities and temperatures, but take a more + structured approach this time. First let's convert the whole list + to Celsius as follows and test the function:</p> + <code type="none"> +-module(tut7). +-export([format_temps/1]). + +format_temps(List_of_cities) -> + convert_list_to_c(List_of_cities). + +convert_list_to_c([{Name, {f, F}} | Rest]) -> + Converted_City = {Name, {c, (F -32)* 5 / 9}}, + [Converted_City | convert_list_to_c(Rest)]; + +convert_list_to_c([City | Rest]) -> + [City | convert_list_to_c(Rest)]; + +convert_list_to_c([]) -> + [].</code> + <pre> +54> <input>c(tut7).</input> +{ok, tut7}. +55> <input>tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},</input> +<input>{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).</input> +[{moscow,{c,-10}}, + {cape_town,{c,21.11111111111111}}, + {stockholm,{c,-4}}, + {paris,{c,-2.2222222222222223}}, + {london,{c,2.2222222222222223}}]</pre> + <p>Looking at this bit by bit:</p> + <code type="none"> +format_temps(List_of_cities) -> + convert_list_to_c(List_of_cities).</code> + <p>Here we see that <c>format_temps/1</c> calls + <c>convert_list_to_c/1</c>. <c>convert_list_to_c/1</c> takes off + the head of the <c>List_of_cities</c>, converts it to Celsius if + needed. The | operator is used to add the (maybe) converted + to the converted rest of the list:</p> + <code type="none"> +[Converted_City | convert_list_to_c(Rest)];</code> + <p>or</p> + <code type="none"> +[City | convert_list_to_c(Rest)];</code> + <p>We go on doing this until we get to the end of the list (i.e. + the list is empty:</p> + <code type="none"> +convert_list_to_c([]) -> + [].</code> + <p>Now we have converted the list, we add a function to print it:</p> + <code type="none"> +-module(tut7). +-export([format_temps/1]). + +format_temps(List_of_cities) -> + Converted_List = convert_list_to_c(List_of_cities), + print_temp(Converted_List). + +convert_list_to_c([{Name, {f, F}} | Rest]) -> + Converted_City = {Name, {c, (F -32)* 5 / 9}}, + [Converted_City | convert_list_to_c(Rest)]; + +convert_list_to_c([City | Rest]) -> + [City | convert_list_to_c(Rest)]; + +convert_list_to_c([]) -> + []. + +print_temp([{Name, {c, Temp}} | Rest]) -> + io:format("~-15w ~w c~n", [Name, Temp]), + print_temp(Rest); +print_temp([]) -> + ok.</code> + <pre> +56> <input>c(tut7).</input> +{ok,tut7} +57> <input>tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},</input> +<input>{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).</input> +moscow -10 c +cape_town 21.11111111111111 c +stockholm -4 c +paris -2.2222222222222223 c +london 2.2222222222222223 c +ok</pre> + <p>We now have to add a function to find the cities with + the maximum and minimum temperatures. The program below isn't + the most efficient way of doing this as we walk through the list + of cities four times. But it is better to first strive for + clarity and correctness and to make programs efficient only if + really needed.</p> + <code type="none"><![CDATA[ +-module(tut7). +-export([format_temps/1]). + +format_temps(List_of_cities) -> + Converted_List = convert_list_to_c(List_of_cities), + print_temp(Converted_List), + {Max_city, Min_city} = find_max_and_min(Converted_List), + print_max_and_min(Max_city, Min_city). + +convert_list_to_c([{Name, {f, Temp}} | Rest]) -> + Converted_City = {Name, {c, (Temp -32)* 5 / 9}}, + [Converted_City | convert_list_to_c(Rest)]; + +convert_list_to_c([City | Rest]) -> + [City | convert_list_to_c(Rest)]; + +convert_list_to_c([]) -> + []. + +print_temp([{Name, {c, Temp}} | Rest]) -> + io:format("~-15w ~w c~n", [Name, Temp]), + print_temp(Rest); +print_temp([]) -> + ok. + +find_max_and_min([City | Rest]) -> + find_max_and_min(Rest, City, City). + +find_max_and_min([{Name, {c, Temp}} | Rest], + {Max_Name, {c, Max_Temp}}, + {Min_Name, {c, Min_Temp}}) -> + if + Temp > Max_Temp -> + Max_City = {Name, {c, Temp}}; % Change + true -> + Max_City = {Max_Name, {c, Max_Temp}} % Unchanged + end, + if + Temp < Min_Temp -> + Min_City = {Name, {c, Temp}}; % Change + true -> + Min_City = {Min_Name, {c, Min_Temp}} % Unchanged + end, + find_max_and_min(Rest, Max_City, Min_City); + +find_max_and_min([], Max_City, Min_City) -> + {Max_City, Min_City}. + +print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) -> + io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]), + io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).]]></code><pre> +58> <input>c(tut7).</input> +{ok, tut7} +59> <input>tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},</input> +<input>{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).</input> +moscow -10 c +cape_town 21.11111111111111 c +stockholm -4 c +paris -2.2222222222222223 c +london 2.2222222222222223 c +Max temperature was 21.11111111111111 c in cape_town +Min temperature was -10 c in moscow +ok</pre> + </section> + + <section> + <title>If and Case</title> + <p>The function <c>find_max_and_min</c> works out the maximum and + minimum temperature. We have introduced a new construct here + <c>if</c>. If works as follows:</p> + <code type="none"> +if + Condition 1 -> + Action 1; + Condition 2 -> + Action 2; + Condition 3 -> + Action 3; + Condition 4 -> + Action 4 +end</code> + <p>Note there is no ";" before <c>end</c>! Conditions are the same + as guards, tests which succeed or fail. Erlang starts at the top + until it finds a condition which succeeds and then it evaluates + (performs) the action following the condition and ignores all + other conditions and action before the <c>end</c>. If no + condition matches, there will be a run-time failure. A condition + which always is succeeds is the atom, <c>true</c> and this is + often used last in an <c>if</c> meaning do the action following + the <c>true</c> if all other conditions have failed.</p> + <p>The following is a short program to show the workings of + <c>if</c>.</p> + <code type="none"> +-module(tut9). +-export([test_if/2]). + +test_if(A, B) -> + if + A == 5 -> + io:format("A == 5~n", []), + a_equals_5; + B == 6 -> + io:format("B == 6~n", []), + b_equals_6; + A == 2, B == 3 -> %i.e. A equals 2 and B equals 3 + io:format("A == 2, B == 3~n", []), + a_equals_2_b_equals_3; + A == 1 ; B == 7 -> %i.e. A equals 1 or B equals 7 + io:format("A == 1 ; B == 7~n", []), + a_equals_1_or_b_equals_7 + end.</code> + <p>Testing this program gives:</p> + <pre> +60> <input>c(tut9).</input> +{ok,tut9} +61> <input>tut9:test_if(5,33).</input> +A == 5 +a_equals_5 +62> <input>tut9:test_if(33,6).</input> +B == 6 +b_equals_6 +63> <input>tut9:test_if(2, 3).</input> +A == 2, B == 3 +a_equals_2_b_equals_3 +64> <input>tut9:test_if(1, 33).</input> +A == 1 ; B == 7 +a_equals_1_or_b_equals_7 +65> <input>tut9:test_if(33, 7).</input> +A == 1 ; B == 7 +a_equals_1_or_b_equals_7 +66> <input>tut9:test_if(33, 33).</input> +** exception error: no true branch found when evaluating an if expression + in function tut9:test_if/2</pre> + <p>Notice that <c>tut9:test_if(33,33)</c> did not cause any + condition to succeed so we got the run time error + <c>if_clause</c>, here nicely formatted by the shell. See the chapter + <seealso marker="doc/reference_manual:expressions">"Guard Sequences"</seealso> in the Erlang Reference Manual for details + of the many guard tests available. <c>case</c> is another + construct in Erlang. Recall that we wrote the + <c>convert_length</c> function as:</p> + <code type="none"> +convert_length({centimeter, X}) -> + {inch, X / 2.54}; +convert_length({inch, Y}) -> + {centimeter, Y * 2.54}.</code> + <p>We could also write the same program as:</p> + <code type="none"> +-module(tut10). +-export([convert_length/1]). + +convert_length(Length) -> + case Length of + {centimeter, X} -> + {inch, X / 2.54}; + {inch, Y} -> + {centimeter, Y * 2.54} + end.</code> + <pre> +67> <input>c(tut10).</input> +{ok,tut10} +68> <input>tut10:convert_length({inch, 6}).</input> +{centimeter,15.24} +69> <input>tut10:convert_length({centimeter, 2.5}).</input> +{inch,0.984251968503937}</pre> + <p>Notice that both <c>case</c> and <c>if</c> have <em>return values</em>, i.e. in the above example <c>case</c> returned + either <c>{inch,X/2.54}</c> or <c>{centimeter,Y*2.54}</c>. + The behaviour of <c>case</c> can also be modified by using guards. + An example should hopefully clarify this. The following example + tells us the length of a month, given the year. We need to know + the year of course, since February has 29 days in a leap year.</p> + <code type="none"> +-module(tut11). +-export([month_length/2]). + +month_length(Year, Month) -> + %% All years divisible by 400 are leap + %% Years divisible by 100 are not leap (except the 400 rule above) + %% Years divisible by 4 are leap (except the 100 rule above) + Leap = if + trunc(Year / 400) * 400 == Year -> + leap; + trunc(Year / 100) * 100 == Year -> + not_leap; + trunc(Year / 4) * 4 == Year -> + leap; + true -> + not_leap + end, + case Month of + sep -> 30; + apr -> 30; + jun -> 30; + nov -> 30; + feb when Leap == leap -> 29; + feb -> 28; + jan -> 31; + mar -> 31; + may -> 31; + jul -> 31; + aug -> 31; + oct -> 31; + dec -> 31 + end.</code> + <pre> +70> <input>c(tut11).</input> +{ok,tut11} +71> <input>tut11:month_length(2004, feb).</input> +29 +72> <input>tut11:month_length(2003, feb).</input> +28 +73> <input>tut11:month_length(1947, aug).</input> +31</pre> + </section> + + <section> + <title>Built In Functions (BIFs)</title> + <p>Built in functions BIFs are functions which for some reason is + built in to the Erlang virtual machine. BIFs often implement + functionality that is impossible to implement in Erlang or is to + inefficient to implement in Erlang. Some BIFs can be called + by use of the function name only but they are by default belonging + to the erlang module so for example the call to the BIF <c>trunc</c> + below is equivalent with a call to <c>erlang:trunc</c>.</p> + <p>As you can see, we first find out if a year is leap or not. If a + year is divisible by 400, it is a leap year. To find this out we + first divide the year by 400 and use the built in function + <c>trunc</c> (more later) to cut off any decimals. We then + multiply by 400 again and see if we get back the same value. For + example, year 2004:</p> + <code type="none"> +2004 / 400 = 5.01 +trunc(5.01) = 5 +5 * 400 = 2000</code> + <p>and we can see that we got back 2000 which is not the same as + 2004, so 2004 isn't divisible by 400. Year 2000:</p> + <code type="none"> +2000 / 400 = 5.0 +trunc(5.0) = 5 +5 * 400 = 2000</code> + <p>so we have a leap year. The next two tests if the year is + divisible by 100 or 4 are done in the same way. The first + <c>if</c> returns <c>leap</c> or <c>not_leap</c> which lands up + in the variable <c>Leap</c>. We use this variable in the guard + for <c>feb</c> in the following <c>case</c> which tells us how + long the month is.</p> + <p>This example showed the use of <c>trunc</c>, an easier way would + be to use the Erlang operator <c>rem</c> which gives the remainder + after division. For example:</p> + <pre> +74> <input>2004 rem 400.</input> +4</pre> + <p>so instead of writing</p> + <code type="none"> +trunc(Year / 400) * 400 == Year -> + leap;</code> + <p>we could write</p> + <code type="none"> +Year rem 400 == 0 -> + leap;</code> + <p>There are many other built in functions (BIF) such as + <c>trunc</c>. Only a few built in functions can be used in guards, + and you cannot use functions you have defined yourself in guards. + (see the chapter + <seealso marker="doc/reference_manual:expressions">"Guard Sequences"</seealso> in the Erlang Reference Manual) (Aside for + advanced readers: This is to ensure that guards don't have side + effects). Let's play with a few of these functions in the shell:</p> + <pre> +75> <input>trunc(5.6).</input> +5 +76> <input>round(5.6).</input> +6 +77> <input>length([a,b,c,d]).</input> +4 +78> <input>float(5).</input> +5.0 +79> <input>is_atom(hello).</input> +true +80> <input>is_atom("hello").</input> +false +81> <input>is_tuple({paris, {c, 30}}).</input> +true +82> <input>is_tuple([paris, {c, 30}]).</input> +false</pre> + <p>All the above can be used in guards. Now for some which can't be + used in guards:</p> + <pre> +83> <input>atom_to_list(hello).</input> +"hello" +84> <input>list_to_atom("goodbye").</input> +goodbye +85> <input>integer_to_list(22).</input> +"22"</pre> + <p>The 3 BIFs above do conversions which would be difficult (or + impossible) to do in Erlang.</p> + </section> + + <section> + <title>Higher Order Functions (Funs)</title> + <p>Erlang, like most modern functional programming languages, has + higher order functions. We start with an example using the shell:</p> + <pre> +86> <input>Xf = fun(X) -> X * 2 end.</input> +#Fun<erl_eval.5.123085357> +87> <input>Xf(5).</input> +10</pre> + <p>What we have done here is to define a function which doubles + the value of number and assign this function to a variable. Thus + <c>Xf(5)</c> returned the value 10. Two useful functions when + working with lists are <c>foreach</c> and <c>map</c>, which are + defined as follows:</p> + <code type="none"> +foreach(Fun, [First|Rest]) -> + Fun(First), + foreach(Fun, Rest); +foreach(Fun, []) -> + ok. + +map(Fun, [First|Rest]) -> + [Fun(First)|map(Fun,Rest)]; +map(Fun, []) -> + [].</code> + <p>These two functions are provided in the standard module + <c>lists</c>. <c>foreach</c> takes a list and applies a fun to + every element in the list, <c>map</c> creates a new list by + applying a fun to every element in a list. Going back to + the shell, we start by using <c>map</c> and a fun to add 3 to + every element of a list:</p> + <pre> +88> <input>Add_3 = fun(X) -> X + 3 end.</input> +#Fun<erl_eval.5.123085357> +89> <input>lists:map(Add_3, [1,2,3]).</input> +[4,5,6]</pre> + <p>Now lets print out the temperatures in a list of cities (yet + again):</p> + <pre> +90> <input>Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",</input> +<input>[City, X, Temp]) end.</input> +#Fun<erl_eval.5.123085357> +91> <input>lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},</input> +<input>{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).</input> +moscow c -10 +cape_town f 70 +stockholm c -4 +paris f 28 +london f 36 +ok</pre> + <p>We will now define a fun which can be used to go through a list + of cities and temperatures and transform them all to Celsius.</p> + <code type="none"> +-module(tut13). + +-export([convert_list_to_c/1]). + +convert_to_c({Name, {f, Temp}}) -> + {Name, {c, trunc((Temp - 32) * 5 / 9)}}; +convert_to_c({Name, {c, Temp}}) -> + {Name, {c, Temp}}. + +convert_list_to_c(List) -> + lists:map(fun convert_to_c/1, List).</code> + <pre> +92> <input>tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},</input> +<input>{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).</input> +[{moscow,{c,-10}}, + {cape_town,{c,21}}, + {stockholm,{c,-4}}, + {paris,{c,-2}}, + {london,{c,2}}]</pre> + <p>The <c>convert_to_c</c> function is the same as before, but we + use it as a fun:</p> + <code type="none"> +lists:map(fun convert_to_c/1, List)</code> + <p>When we use a function defined elsewhere as a fun we can refer + to it as <c>Function/Arity</c> (remember that <c>Arity</c> = + number of arguments). So in the <c>map</c> call we write + <c>lists:map(fun convert_to_c/1, List)</c>. As you can see + <c>convert_list_to_c</c> becomes much shorter and easier to + understand.</p> + <p>The standard module <c>lists</c> also contains a function + <c>sort(Fun, List)</c> where <c>Fun</c> is a fun with two + arguments. This fun should return <c>true</c> if the the first + argument is less than the second argument, or else <c>false</c>. + We add sorting to the <c>convert_list_to_c</c>:</p> + <code type="none"><![CDATA[ +-module(tut13). + +-export([convert_list_to_c/1]). + +convert_to_c({Name, {f, Temp}}) -> + {Name, {c, trunc((Temp - 32) * 5 / 9)}}; +convert_to_c({Name, {c, Temp}}) -> + {Name, {c, Temp}}. + +convert_list_to_c(List) -> + New_list = lists:map(fun convert_to_c/1, List), + lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> + Temp1 < Temp2 end, New_list).]]></code> + <pre> +93> <input>c(tut13).</input> +{ok,tut13} +94> <input>tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},</input> +<input>{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).</input> +[{moscow,{c,-10}}, + {stockholm,{c,-4}}, + {paris,{c,-2}}, + {london,{c,2}}, + {cape_town,{c,21}}]</pre> + <p>In <c>sort</c> we use the fun:</p> + <code type="none"><![CDATA[ +fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,]]></code> + <p>Here we introduce the concept of an <em>anonymous variable</em> + "_". This is simply shorthand for a variable which is going to + get a value, but we will ignore the value. This can be used + anywhere suitable, not just in fun's. <c><![CDATA[Temp1 < Temp2]]></c> + returns <c>true</c> if <c>Temp1</c> is less than <c>Temp2</c>.</p> + </section> +</chapter> + diff --git a/system/doc/getting_started/xmlfiles.mk b/system/doc/getting_started/xmlfiles.mk new file mode 100644 index 0000000000..c784d79dc3 --- /dev/null +++ b/system/doc/getting_started/xmlfiles.mk @@ -0,0 +1,24 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +GETTING_STARTED_CHAPTER_FILES = \ + conc_prog.xml \ + intro.xml \ + records_macros.xml \ + robustness.xml \ + seq_prog.xml |