===================== Erlang Course Notes ===================== :author: Dave Kuhlman :contact: dkuhlman (at) davekuhlman (dot) org :address: http://www.davekuhlman.org :revision: 1.0a :date: |date| .. |date| date:: %B %d, %Y :Copyright: Copyright (c) 2016 Dave Kuhlman. All Rights Reserved. This software is subject to the provisions of the MIT License http://www.opensource.org/licenses/mit-license.php. :Abstract: This document provides notes and how-to information on programming in Erlang. It can hopefully serve as a cheat sheet on Erlang programming. .. sectnum:: .. contents:: ---------- Introductions Etc =================== This is a set of usage notes on Erlang. As with any programming language, some of the things that you need to know are hard to find or slow to search for and lookup. This document will hopefully save you a bit of time with that. This document can also be viewed as an introductory how-to for Erlang programming. Help and docs --------------- The Erlang standard documentation is here: - With a search box: http://erlang.org/erldoc - Without a search box: http://erlang.org/doc/ You might also consider downloading the Erlang document and unrolling it on your local machine, which will enable you to jump around among various links more quickly. You can find that here: http://www.erlang.org/downloads. There is a FAQ and more docs here: http://www.erlang.org/docs. The ``erl`` interactive shell ------------------------------- Start it as follows:: $ erl Erlang/OTP 19 [erts-8.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] Eshell V8.0 (abort with ^G) 1> On Linux, you might want to install and use ``rlwrap``. It saves history across sessions. I use the following ``bash`` alias to start the ``erl`` shell:: alias erlrl='rlwrap -a -c -r erl' Or, if you don't want the alias, just start the shell as follows:: $ rlwrap -a -c -r erl For more information about the Erlang shell (emulator), see: http://erlang.org/doc/man/erl.html. On MS Windows, you will want to consider using ``werl`` instead of ``erl``. ``werl`` has some GUI (graphical user interface) features that as not provided by plain ``erl``. Compiling to .beam files -------------------------- In the interactive shell use:: 7> c(test01). {ok,test01} You can also pass in a compiler option. For example, to compile a file for use in the Erlang debugger, use something like this:: 8> c(test01, [debug_info]). {ok,test01} At the command line, use ``erlc`` (http://erlang.org/doc/man/erlc.html):: $ erlc test01.erl And, with a compiler option:: $ erlc +debug_info test01.erl Debugging and the Erlang debugger ----------------------------------- Erlang has a very nice visual/graphical debugger. And, it enables you to step through multiple, concurrent, Erlang processes. In the Erlang shell, use the internal shell command ``ih()`` to get more help with the debugger. Example:: 21> ih(). iv() -- print the current version of the interpreter im() -- pop up a monitor window ii(Mod) -- interpret Mod(s) (or AbsMod(s)) ii(Mod,Op) -- interpret Mod(s) (or AbsMod(s)) o o o To use the debugger: 1. Compile your modules with the ``debug_info`` option. In the Erlang shell, use ``c(my_module, [debug_info]).``. From the command line, use ``erlc +debug_info my_module``. 2. Start the Erlang shell:: $ erl 3. Start the debugger:: 2> im(). <0.36.0> 4. Load your module into the debugger:: 3> ii(ca02). {module,ca02} Alternatively, you could load your module by using the ``Module`` menu in the debugger Monitor window. 5. Set a breakpoint. You can do this in the Monitor window by selecting the ``Module`` menu, then your module, and then ``View``. Or, alternatively, double click the module name in the top left pane of the Monitor window. A debugger view window will appear. Then, double click on the line where you want to set a breakpoint. 6. Run your test. Now, back in the Erlang shell, call the function or functions that run your test. For example:: 4> {ok, G} = ca02:initialize("test04.csv"). 7. Step through, inspect, and debug your code. If the debugger arrived at one of your breakpoints, then in the debugger Monitor window, that process should be listed with a status of "break". Double click on that line, and the debugger will present a window where you can begin stepping through and inspecting the behavior of that process. Built-in functions ==================== The documentation on BIFs is here: http://erlang.org/doc/apps/erts/index.html. Expressions and operators =========================== Information on various kinds of expressions can be found here: http://erlang.org/doc/reference_manual/expressions.html. Information on operators can be found in that section. Look in sub-sections titled "Term comparisons", "Arithmetic expressions", and "Boolean expressions". Built-in data types ===================== Additional information on Erlang data types can be found here: http://erlang.org/doc/reference_manual/data_types.html. Atoms ------- Atoms are self-defining names. They either begin with a lower case letter, or are surrounded by single quotes. Examples:: 5> A = abc. abc 6> B = 'aa.bb.cc'. 'aa.bb.cc' 7> B. 'aa.bb.cc' 8> A. abc You can use the ``is_atom`` BIF (built-in function) to test for an atom. Tuples -------- A tuple is defined by zero or more terms inside curly brackets. Examples:: 9> {}. {} 10> {aa}. {aa} 11> {aa, bb, cc}. {aa,bb,cc} 12> 12> C = {nil, nil, [11, 22, 33], nil}. {nil,nil,[11,22,33],nil} You can use the ``is_tuple`` BIF (built-in function) to test for an tuple. Lists -------- A list is defined by zero or more terms inside square brackets. Examples:: 13> f(). ok 14> A = [100, 200, 300]. [100,200,300] 15> B = [aaa, A, bbb]. [aaa,[100,200,300],bbb] 16> 16> C = ["abc", "def", "ghi"]. ["abc","def","ghi"] 17> 17> D = "bcdef". "bcdef" 18> D1 = ["a" | D]. ["a",98,99,100,101,102] 19> lists:flatten(D1). "abcdef" Notes: - The ``f()`` shell function causes the shell to forget all our variable bindings in the shell, so that we can bind those variables again. Use ``help()`` to get a list of additional shell internal commands. - Remember that a string is a list of characters. So, if we "flatten" a list that contains characters and a list of characters, we get a list of characters, which is a string. - We can use functions from the ``lists`` module on strings, because they are lists. See: http://erlang.org/doc/man/lists.html. - Lists can, of course, be nested inside of lists, and to any depth. You can use the ``is_list`` BIF (built-in function) to test for an list. Use the BIFs ``hd`` and ``tl`` to access the head and tail of a list. Examples:: 10> C. [11,22,33] 11> hd(C). 11 12> tl(C). [22,33] Use the ``[X | Y]`` construction to build a list by appending an item to the head of a list or (2) to deconstruct a list into its head and tail. Examples:: 1> A = 11. 11 2> B = [22, 33]. [22,33] 3> C = [A | B]. [11,22,33] 4> C. [11,22,33] 5> 5> f(). ok 6> C = [11, 22, 33]. [11,22,33] 7> [A | B] = C. [11,22,33] 8> A. 11 9> B. [22,33] This is especially useful when writing recursive functions on lists. Example:: list_test01([], Accum) -> Accum; list_test01([Head | Tail], Accum) -> Accum1 = Accum + Head, list_test01(Tail, Accum1). When we run the above we might see:: 2> test01:list_test01([11, 22, 33, 44], 0). 110 Strings --------- Strings in Erlang are just lists of characters. So, many of the functions in the ``lists`` module are helpful with strings, too. Some examples:: 37> f(). ok 38> 38> A = "abcdef". "abcdef" 39> length(A). 6 40> lists:reverse(A). "fedcba" 41> io:fwrite("Variable A: ~p~n", [A]). Variable A: "abcdef" ok 42> 42> io:fwrite("Variable A: ~s~n", [A]). Variable A: abcdef ok Notes: - The function to determine the length of a list or a string is ``length/1``, and it's a BIF (built in function), not a function in the ``lists`` module. - Strings are formatted differently depending on whether we use the ``~p`` or the ``~s`` format specifier. To iterate over the characters in a string, use a standard Erlang recursive function, just like you would with a list:: -module(test01). -export([double_chars/2]). double_chars([], Accum) -> lists:reverse(Accum); double_chars([Char | MoreChars], Accum) -> Accum1 = [Char | Accum], Accum2 = [Char | Accum1], double_chars(MoreChars, Accum2). When we call it, we see:: 1> c(test01). {ok,test01} 2> A = "abcdef". "abcdef" 3> test01:double_chars(A, []). "aabbccddeeff" Notes: - The first clause of ``double_chars/2`` is our stop or terminating clause. It checks for the terminating condition (and empty list, in this case), then reverses and returns the list of characters (i.e. the string. - The second clause strips one character off the input list, appends that character twice to the accumulator (a list), and then calls itself (the recursive part) with the remainder of the characters and this accumulated list. Formatting and printing strings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To format *and* print a string, use ``io:format`` or ``io:fwrite``: http://erlang.org/doc/man/io.html. They do the same thing. To format a string (but not print it), use ``iolib:format``: http://erlang.org/doc/man/io_lib.html. Examples:: 44> A = "red". "red" 45> B = "yellow". "yellow" 46> 46> io:format("The ~s apple and the ~s banana", [A, B]). The red apple and the yellow bananaok 47> 47> io:format("The ~s apple and the ~s banana~n", [A, B]). The red apple and the yellow banana ok There is no test for the "string data type", since strings in Erlang are lists, though you could use the ``is_list`` BIF to at least determine whether it is a list. Maps --------- A map associates keys with values. **Create a map** -- Example:: 17> A = #{aa=>11, bb=>22, cc=>33, 12=>"abc", "doc"=>"bark"}. #{12 => "abc",aa => 11,bb => 22,cc => 33,"doc" => "bark"} 18> A. #{12 => "abc",aa => 11,bb => 22,cc => 33,"doc" => "bark"} **Update a map** -- Example:: 20> A. #{12 => "abc",aa => 11,bb => 22,cc => 33,"doc" => "bark"} 21> 21> A1 = A#{aa=>111}. #{12 => "abc",aa => 111,bb => 22,cc => 33,"doc" => "bark"} 22> A1. #{12 => "abc",aa => 111,bb => 22,cc => 33,"doc" => "bark"} **Update an existing key in a map** -- If you want to update an existing key in a map, and you want an error thrown if the key does not exist, use the following syntax:: 23> A. #{12 => "abc",aa => 11,bb => 22,cc => 33,"doc" => "bark"} 24> B = A#{bb := 222}. #{12 => "abc",aa => 11,bb => 222,cc => 33,"doc" => "bark"} 25> 25> B1 = A#{xx := 222}. ** exception error: {badkey,xx} in function maps:update/3 called as maps:update(xx,222,#{12 => "abc",aa => 11,bb => 22,cc => 33,"doc" => "bark"}) in call from erl_eval:'-expr/5-fun-0-'/2 (erl_eval.erl, line 255) in call from lists:foldl/3 (lists.erl, line 1263) **Pattern matching on maps** -- We can bind variables to the values of keys in a map. Examples:: 37> A. #{12 => "abc",aa => 11,bb => 22,cc => 33,"doc" => "bark"} 38> #{cc := C, bb := B} = A. #{12 => "abc",aa => 11,bb => 22,cc => 33,"doc" => "bark"} 39> B. 22 40> C. 33 There are functions for working with maps in the ``maps`` module in the standard library: http://erlang.org/doc/man/maps.html. For more information on maps, see: http://erlang.org/doc/reference_manual/expressions.html#map_expressions. Funs ------- **Define a fun** -- We define a ``fun`` by using the ``fun ... end`` keywords. Example:: 41> F1 = fun (X, Y) -> io:format("X: ~p Y: ~p~n", [X, Y]), ok end. #Fun 42> 42> F1(11, 22). X: 11 Y: 22 ok **Multiple clauses** -- A ``fun`` can have multiple clauses. Pattern matching on the heads of the clauses is performed in the same way as for functions. Example:: fun_test01(A, B) -> F = fun (key1, X) -> io:fwrite("key1 -- X: ~p~n", [X]), gotkey1; (key2, X) -> io:fwrite("key2 -- X: ~p~n", [X]), gotkey2 end, Reply = F(A, B), Reply. When we call the above, we'd see:: 44> c(test01). {ok,test01} 45> test01:fun_test01(key1, 45). key1 -- X: 45 gotkey1 46> test01:fun_test01(key2, 55). key2 -- X: 55 gotkey2 47> 47> 47> test01:fun_test01(key3, 55). ** exception error: no function clause matching test01:'-fun_test01/2-fun-0-'(key3,55) (test01.erl, line 166) 48> **Named fun** -- A ``fun`` definition can provide a name. That enables us to call the ``fun`` recursively, that is, from within the ``fun`` itself. Example:: fun_test02(Data) -> F = fun Travel ([], Accum) -> Accum; Travel ([Item | Moredata], Accum) -> Travel(Moredata, Accum + Item) end, Result = F(Data, 0), Result. When we call the above function, we might see:: 1> c(test01). {ok,test01} 2> test01:fun_test02([44, 33, 22, 11]). 110 Notes: - In a named ``fun``, the name must be a variable, i.e. start with an upper case letter. **Define a fun by name and arity** -- We can define a ``fun`` by using an existing function and it's arity, for example ``Name/Arity`` or ``Module:Name/Arity``. This gives us an easy way to produce a ``fun`` from an exiting function. Example:: fun_test03_helper(X, Y) -> Sum = X + Y, io:fwrite("X: ~p Y: ~p Sum: ~p~n", [X, Y, Sum]), Sum. fun_test03(V1, V2) -> F = fun fun_test03_helper/2, Result = F(V1, V2), Result. When we call ``fun_test03/2``, we might see:: fun_test03_helper(X, Y) -> Sum = X + Y, io:fwrite("X: ~p Y: ~p Sum: ~p~n", [X, Y, Sum]), Sum. fun_test03(V1, V2) -> F = fun fun_test03_helper/2, Result = F(V1, V2), Result. For more information on ``fun``, see: http://erlang.org/doc/reference_manual/expressions.html#funs. Functions =========== A function definition can have multiple clauses separated by semi-colons. The head of each clause is composed of the function name and a list of arguments. Each heads of the clauses in a single function definition must have the same arity. For example, here is a function that simulates the ``map`` function from the ``lists`` module ``stdlib``. This example defines two functions: ``map/2`` and ``map/3``, one with arity 1 and the other with arity 3. The function ``map/3`` has two clauses: one checks for an empty list and the other processes one item from the list:: map(Func, Items) -> map(Func, Items, []). map(_, [], Accum) -> lists:reverse(Accum); map(Func, [Item | MoreItems], Accum) -> Result = Func(Item), map(Func, MoreItems, [Result | Accum]). Notes: - Erlang is able to select the correct function (``map/2`` or ``map/31``) depending on how many arguments we pass to it. - The second function (``map/3``) has two clauses. Erlang select one or the other depending on whether the second argument is an empty list. - Function ``map/3`` is recursive, i.e. it calls itself. Furthermore, it is *tail* recursive: since no computation is done after the recursive call, it consumes no additional space on the function call stack for additional calls to itself. Records ========= Records are just a wrapper around tuples. **Define a record** -- Use the ``-record(name, [field, ...]).`` instructive to define a record: - ``name`` is an atom. - Each field is of one of the following forms: (1) ``atom`` or (2) ``atom=initial_value``, where ``initial_value`` is the default value of that field if no value is given when an instance of the record is created. Examples:: -record(celldata, { state, % State = 0 | 1 nextstate, % State = 0 | 1 neighbors, % [Pid, ...] neighborstates % [{Pid, State}, ...] }). **Create an instance of a record** -- This example shows how to create an instance of a record, given its definition:: 14> rd(kitten, {name, color}). kitten 15> Kitten1 = #kitten{name=taffy, color=carmel}. #kitten{name = taffy,color = carmel} 16> 16> rd(puppy, {name, size=medium}). puppy 17> Puppy1 = #puppy{name=frisky}. #puppy{name = frisky,size = medium} 18> 18> rl(). -record(kitten,{name,color}). -record(puppy,{name,size = medium}). ok Notes: - Since this example was done in the Erlang shell, we use the ``rd/2`` shell function to define a record. - The ``rl/0`` shell function produces a list the record definitions. - Type ``help().`` in the Erlang shell to learn about additional shell functions for working with records. **Access (get) the value of a field** -- We can extract the value of a field either (1) using a "dot" notation or (2) using pattern matching. Examples:: rec_test4() -> R1 = #record1{field1=111, field2=[aa,bb], field3=333}, io:fwrite("R1: ~p~n", [R1]), io:fwrite("R1.field2: ~p~n", [R1#record1.field2]), #record1{field2=Fld2, field3=Fld3} = R1, io:fwrite("Fld2: ~p Fld2: ~p~n", [Fld2, Fld3]), ok. **Replace the value of a field** -- Reuse the old record, but replace the values of selected fields to create a new record. Examples:: rec_test1() -> R1 = #record1{}, R2 = R1#record1{field1=100}, io:fwrite("R1: ~p~n", [R1]), io:fwrite("R2: ~p~n", [R2]), ok. rec_test2() -> R1 = #record1{field1=11, field3=33}, R2 = R1#record1{field1=25, field3=50}, io:fwrite("R1: ~p~n", [R1]), io:fwrite("R2: ~p~n", [R2]), ok. rec_test3() -> R1 = #record1{field1=11, field2=[aa,bb,cc], field3=33}, R2 = R1#record1{field2=[zz | R1#record1.field2]}, io:fwrite("R1: ~p~n", [R1]), io:fwrite("R2: ~p~n", [R2]), ok. Notes: - Erlang's rules about only one binding per variable require us to create a new record rather than modifying an existing record. - We can update more than one field at once, as in ``rec_test2/0``. - We can update a field using the existing value of that field (or another field). Statements ============ case statement ---------------------- A case statement is composed of multiple clauses of the form: ``head --> body``. The first clause whose head matches the target value is then evaluated. The value of the case statement is the value of the last expression in the body of the evaluated clause. Example:: case_test(Arg) -> Reply = case Arg of {cmd1, Command, Arg1} -> io:fwrite("cmd1 -- Command: ~p Arg1: \"~p\"~n", [Command, Arg1]), reply1; {cmd2, Command, Arg1} -> io:fwrite("cmd2 -- Command: ~p Arg1: \"~p\"~n", [Command, Arg1]), reply2; Stuff -> io:fwrite("catch-all -- Stuff: ~p~n", [Stuff]), misc end, Reply. When we call the above, we might see:: 2> test01:case_test(abc). catch-all -- Stuff: abc misc 3> test01:case_test([aaa, bbb, ccc]). catch-all -- Stuff: [aaa,bbb,ccc] misc 4> test01:case_test({cmd1, push, [11, 22, 33, 44]}). cmd1 -- Command: push Arg1: "[11,22,33,44]" reply1 Notes: - We can use tuples (or lists) to pass in multiple values. - The matching process is very similar to that done on the head of a function. - As in the matching process performed on the heads of clauses of functions, it is possible to bind values to variables while matching the head of a clause. - In our sample code, the case statement returns one of ``reply1``, ``reply2``, or ``misc``, and binds that value to the variable ``Reply``. if statement ---------------------- The ``if`` statement is similar to the ``case`` statement in some ways, but uses boolean expressions rather than pattern matching. The boolean expressions that form the heads of clauses in the ``if`` statement are very much like guards expressions. Example:: if_test(Arg) -> Reply = if Arg >= 4 -> io:fwrite("Arg ~p greater than or equal to 4~n", [Arg]), {greater, Arg}; (Arg =< 3) and (Arg >= 1) -> io:fwrite("Arg ~p between 1 and 3~n", [Arg]), {less, Arg}; Arg == 0 -> io:fwrite("Arg ~p is zero~n", [Arg]), {equal, Arg}; Arg == -1; Arg == -2 -> io:fwrite("Arg ~p is -1 or -2~n", [Arg]), {equal, Arg}; true -> io:fwrite("Unexplected value -- Arg: ~p~n", [Arg]), {error, "unexpected"} end, Reply. And, when we call the above, we see:: 18> test01:if_test(0). Arg 0 is zero {equal,0} 19> test01:if_test(6). Arg 6 greater than or equal to 4 {greater,6} 20> test01:if_test(2). Arg 2 between 1 and 3 {less,2} 21> test01:if_test(-8). Unexplected value -- Arg: -8 {error,"unexpected"} Notes: - We can use any boolean expressions, even complex ones for the heads of our clauses. However, these expressions must satisfy the rules for guard expressions. See here for a description of guards: http://erlang.org/doc/reference_manual/expressions.html (and look for "Guard Sequences"). - Notice that complex expressions (for example, those that contain boolean operators ``and`` and ``or``) often require parentheses. That's because ``and`` and ``or`` have higher precedence/priority (bind more tightly) than operators such as ``==``, ``>=``, etc. See: http://erlang.org/doc/reference_manual/expressions.html (and look for "Operator Precedence"). - The head of a clause may contain multiple expressions separated by semicolons, in which case that clause is selected if any of those expressions are true. For example, notice the head ``Arg == -1; Arg == -2 ->`` in our sample code, above. - We can use the expression ``true`` as a catch-all head, which effectively gives us an "else" clause. receive statement ---------------------- See `Processes`_. try ... catch etc ------------------- The ``try ... catch`` statement enables you to "protect" a block of code and to "catch" an exception if it occurs in that code. Example:: test_try(Arg) -> try A = Arg + 3, {ok, A} of {ok, Result} -> io:format("Result: ~p~n", [Result]); Junk -> io:format("Junk: ~p~n", [Junk]) catch error:Excp -> io:format("Excp: ~p~n", [Excp]) end, io:format("finished~n"), ok. Here is what we might see when we call this function:: 17> c(test01). {ok,test01} 18> 18> test01:test_try(25). Result: 28 finished ok 19> test01:test_try("abcd"). Excp: badarith finished ok Notes: - The ``of`` keyword and block is optional. - If no exception occurs in the ``try`` block, the ``of`` block is given the result of the ``try`` block and can deal with it in a way that is similar to a ``case ... of`` statement. - If an exception does occur in the ``try`` block, then the ``of`` block is ignored and the exception is given to the ``catch`` block. If the ``catch`` block contains a clause that matches the exception, then that clause is evaluated and the exception is considered to have been handled. If no clause in the ``catch`` block matches the exception, then the exception "bubbles up" through the chain of function calls until it is either caught and handled (in another ``try ... catch`` statement) or is reported by Erlang. - Suggestion -- To find out what exception you should try to catch, you can mimic the exception in the Erlang shell. For example:: 23> throw(abcd). ** exception throw: abcd 24> 24> erlang:error("bad kittie"). ** exception error: "bad kittie" 25> 25> 3 + "abc". ** exception error: an error occurred when evaluating an arithmetic expression in operator +/2 called as 3 + "abc" The characters that appear immediately after "** exception " are the name of the exception that you need to catch. In the above example, these exceptions could be caught with ``throw:Excp`` and ``error:Excp``, where ``Excp`` is an unbound variable. - There is also plain ``catch`` and ``catch ... of``, but its use is not recommended and should only be used in legacy code. begin ... end ---------------- You can group a sequence of statements together with ``begin ... end``. Here is an example:: 31> begin 31> io:format("one~n"), 31> io:format("two~n"), 31> io:format("three~n") 31> end. one two three ok Processes =========== A process is an Erlang function that has been spawned. The process is "alive" as long as it continues to run. We communicate with a process by sending messages to it. A process receives messages by using the ``receive`` statement. Example:: process_test() -> %Pid = spawn(fun () -> process_loop(0) end), Pid = spawn(?MODULE, process_loop, [0]), Pid ! {msg1, self(), "hello"}, receive {ok, ReplyMsg1} -> io:format("(process_test) received \"~s\"~n", [ReplyMsg1]) end, Pid ! {msg2, self(), "goodbye"}, receive {ok, ReplyMsg2} -> io:format("(process_test) received \"~s\"~n", [ReplyMsg2]) end, Pid ! stop, ok. process_loop(Count) -> receive {msg1, FromPid, Msg} -> io:format("~p. received message 1 -- Msg: \"~s\"~n", [Count, Msg]), ReplyMsg = io_lib:format("processed message number ~B", [Count]), FromPid ! {ok, ReplyMsg}, process_loop(Count + 1); {msg2, FromPid, Msg} -> io:format("~p. received message 2 -- Msg: \"~s\"~n", [Count, Msg]), ReplyMsg = io_lib:format("processed message number ~B", [Count]), FromPid ! {ok, ReplyMsg}, process_loop(Count + 1); stop -> io:format("exiting~n"), ok end. And, when we call function ``process_test/0``, we see:: 5> c(test01). {ok,test01} 6> test01:process_test(). 0. received message 1 -- Msg: "hello" (process_test) received "processed message number 0" 1. received message 2 -- Msg: "goodbye" (process_test) received "processed message number 1" exiting ok Notes: - ``process_test/0`` spawns a second process that is implemented by the function ``process_loop/1``. - ``process_loop/1`` tries to receive and process a message (``{msg1, ...}`` or ``{msg2, ...}``), and then keeps trying to do that over and over, until it receives a ``stop`` message. - When ``process_loop/1`` receives ``stop`` message, instead of calling itself to continue processing, it exits, which ends (kills) that processes. Rules and guidance for the ``receive`` statement: - Each process has its own message queue. - The messages are processed in first-come-first-served (FIFO) order. - After a message is processed (received), it is removed from the queue. - The heads of the clauses in the ``receive`` statement are matched against the first message; the clause of the first head that matches is selected and evaluated. - If none of the heads of the clauses in the ``receive`` statement matches the first message in the queue, then the second message is tried. This is repeated, if necessary until one message is matched and processed. - Notice that, if the ``receive`` statement is inside a loop, each time the ``receive`` statement is executed, it starts with the oldest (first sent) message. And, each time the ``receive`` statement is executed, it processes *at most* one message. It processes zero messages if none of the heads of any clause matches any of the messages in the process's message queue. ETS and DETS ============== ETS ----- ETS enables you to create an in-memory lookup table. **Create a table** -- Here are some of your options: - ``named_table`` -- Enables you to refer to the table by the atom used to create it, rather than needing the process ID returned when it's created, thus enabling you to use the table in scattered parts of your code without passing the process ID. - ``set`` | ``ordered_set`` | ``bag`` | ``duplicate_bag`` -- Determines how many values per key, how the keys are ordered, etc. For definitions of these options, see here: http://erlang.org/doc/man/ets.html#new-2. Examples:: 14> P1 = ets:new(t1, [set]) -> 28688 15> ets:insert(P1, {aa,11}) -> true 16> ets:insert(P1, {bb,22}) -> true 17> ets:insert(P1, {aa,101}) -> true 18> ets:lookup(P1, aa) -> [{aa,101}] 19> f(P1) -> ok 20> P1 = ets:new(t2, [bag]) -> 32785 21> ets:insert(P1, {aa,11}) -> true 22> ets:insert(P1, {aa,1001}) -> true 23> ets:lookup(P1, aa) -> [{aa,11},{aa,1001}] Notes: - The first table is a set. So, when we associate a second value with the key ``aa``, only that second value is saved. - The second table is a bag. So, when we associate (insert) multiple values with the same key, all those associations are saved. ETS has many more functions than those shown here. See the following for information about this library: http://erlang.org/doc/man/ets.html DETS ------ DETS is very much like ETS, but DETS tables are stored in a disk file. This means that (1) they are slower and (2) they are persistent and can be accessed in a subsequent session. ** Create and use a DETS table** -- Use ``dets:open_file/3``. See; http://erlang.org/doc/man/dets.html#open_file-1. Examples:: 27> dets:open_file(t1, [{file, "t1.dets"}, {type, bag}]). {ok,t1} 28> dets:insert(t1, {aa, 1111}). ok 29> dets:insert(t1, {aa, 1112}). ok 30> dets:insert(t1, {aa, 1113}). ok 31> dets:insert(t1, {aa, 1114}). ok 32> dets:close(t1). ok And, possibly, much later:: 33> dets:open_file(t1, [{file, "t1.dets"}, {type, bag}]). {ok,t1} 34> dets:lookup(t1, aa). [{aa,1111},{aa,1112},{aa,1113},{aa,1114}] 35> dets:close(t1). ok Mnesia ======== In order to use Mnesia, we need to do the following: 1. Initialize Mnesia. 2. Start your node. 3. Create the schema. 4. Start Mnesia 5. Create the database tables. 6. Populate the tables, make queries on the tables, etc. There are many more Mnesia functions than the ones I discuss in this section. To learn about them, visit: http://erlang.org/doc/apps/mnesia/index.html. **Initialize Mnesia** -- I use this script to start Mnesia:: #!/bin/bash rlwrap -a -c -r \ erl -mnesia dir '"/tmp/mnesia_data"' \ -name session1@crow.local \ -setcookie aaa **Create the schema** -- Use this:: (session1@crow.local)3> mnesia:create_schema([node()]). **Start Mnesia** -- In order to start Mnesia and get information about it, use this:: (session1@crow.local)1> mnesia:start(). ok (session1@crow.local)2> mnesia:info(). ---> Processes holding locks <--- ---> Processes waiting for locks <--- ---> Participant transactions <--- ---> Coordinator transactions <--- ---> Uncertain transactions <--- ---> Active tables <--- schema : with 1 records occupying 407 words of mem ===> System info in version "4.14", debug level = none <=== opt_disc. Directory "/tmp/mnesia_data" is used. use fallback at restart = false running db nodes = ['session1@crow.local'] stopped db nodes = [] master node tables = [] remote = [] ram_copies = [] disc_copies = [schema] disc_only_copies = [] [{'session1@crow.local',disc_copies}] = [schema] 2 transactions committed, 0 aborted, 0 restarted, 0 logged to disc 0 held locks, 0 in queue; 0 local transactions, 0 remote 0 transactions waits for other nodes: [] ok **Define the records** -- Since we will need these record definitions in several files, we'll put them in a separate include file -- ``mnesia_tables.hrl``:: -record(recipe, {name, ingredients}). -record(fruit, {name, color}). **Create the tables** -- I used the following module:: -module(create_mnesia_tables). -export([ create_tables/0 ]). -include("mnesia_tables.hrl"). create_tables() -> mnesia:create_table(recipe, [{attributes, record_info(fields, recipe)}]), mnesia:create_table(fruit, [{attributes, record_info(fields, fruit)}]), ok. **Populate the tables** -- Here is a module that can put some data in our tables:: -module(populate_mnesia_tables). -export([ populate_tables/0 ]). -include("mnesia_tables.hrl"). populate_tables() -> populate_recipe(), populate_fruit(), ok. populate_recipe() -> Records = [ #recipe{name=marmalade, ingredients=[sugar, mandarins, lemons]}, #recipe{name=pancakes, ingredients=[flour, mil, yeast, eggs]} ], write_recs(Records), ok. populate_fruit() -> Records = [ #fruit{name=lemon, color=yellow}, #fruit{name=strawberry, color=pink}, #fruit{name=blueberry, color=pale_blue} ], write_recs(Records), ok. write_recs([]) -> ok; write_recs([Rec | MoreRecs]) -> F = fun() -> mnesia:write(Rec) end, mnesia:transaction(F), write_recs(MoreRecs). **Read the tables** -- Now we can read data from our tables with the following:: (session1@crow.local)17> mnesia:transaction(fun () -> mnesia:read(fruit, lemon) end). {atomic,[{fruit,lemon,yellow}]} (session1@crow.local)18> mnesia:transaction(fun () -> mnesia:read(fruit, blueberry) end). {atomic,[{fruit,blueberry,pale_blue}]} (session1@crow.local)19> mnesia:transaction(fun () -> mnesia:read(fruit, watermelon) end). {atomic,[]} (session1@crow.local)20> mnesia:transaction(fun () -> mnesia:read(recipe, pancakes) end). {atomic,[{recipe,pancakes,[flour,mil,yeast,eggs]}]} Unit testing -- eunit ======================= **Basic use** -- For simple use cases, ``eunit`` is quite simple. Add this directive to your source code file immediately after the ``-module`` directive:: -include_lib("eunit/include/eunit.hrl"). Then every function whose name ends with "_test" or "_test_", becomes a test case. Here is an example:: -module(test_gen_fsm_eunit). -include_lib("eunit/include/eunit.hrl"). a1_test() -> ok = test_gen_fsm:test(), ok. a2_test() -> nil = test_gen_fsm:test(), ok. a3_test() -> ?assert(test_gen_fsm:test() =:= ok), ok. a4_test() -> ?assert(test_gen_fsm:test() =:= none), ok. **Run the tests** -- You can run all these tests with the following:: 3> my_module:test(). Or, alternatively:: 4> eunit:test(my_module). And, you can get a bit more information by running:: 5> eunit:test(my_module, [verbose]). **Macros** -- The example above used the ``?assert`` macro. You can learn about other ``eunit`` macros here: http://erlang.org/doc/apps/eunit/chapter.html, then look for "Macros". **Fixtures** -- For more complicated uses, you will want to try ``eunit`` fixtures. Importantly, fixtures enable you to specify set-up and clean-up (tear-down), code to be run just before and after each of a set of tests. An extensive and helpful set of slides containing many examples of the use of fixtures is here: http://www.erlang-factory.com/upload/presentations/33/EUnitinPractice.pdf Hints, tips, and suggestions ============================== Text editor support --------------------- Vim comes with a good Erlang mode. See the following for additional plugins that are helpful for editing Erlang code in Vim: https://github.com/vim-erlang. I believe that there is a good Erlang mode for Emacs. .. vim: ft=rst: