├── Chapter 10 ├── exercise_1.md └── modern_makefile ├── Chapter 12 ├── exercise 1.md ├── exercise 2.md ├── exercise 3.md └── runtimes.png ├── Chapter 13 ├── _running_programs.md ├── exercise 1.md ├── exercise 2.md ├── exercise 3.md ├── exercise 4.md ├── exercise 5.md └── exercise 6.md ├── Chapter 14 ├── exercise 1.md └── exercise 2.md ├── Chapter 15 ├── _main_example_explanation.md └── exercise 1.md ├── Chapter 17 ├── exercise 1.md └── exercise 2.md ├── Chapter 18 └── simple_example.md ├── Chapter 3 └── exercise 3.md ├── Chapter 4 ├── exercise 1.md ├── exercise 2.md ├── exercise 3.md ├── exercise 4.md ├── exercise 5.md ├── exercise 6.md └── exercise 7.md ├── Chapter 5 ├── exercise 1.md ├── exercise 2.md └── exercise 3.md ├── Chapter 6 ├── exercise 1.md └── exercise 2.md ├── Chapter 7 ├── exercise 1.md ├── exercise 2.md ├── exercise 3.md ├── exercise 4.md ├── exercise 5.md └── mp3.md ├── Chapter 8 ├── exercise 1.md └── exercise 2.md ├── Chapter 9 ├── exercise 1.md ├── exercise 3.md └── exercise 4.md └── README.md /Chapter 10/exercise_1.md: -------------------------------------------------------------------------------- 1 | If you haven't tried **eunit** for testing yet, the simplest way to use **eunit** is to run all the functions named \*_test in a file. For example, suppose you have a file like this: 2 | 3 | ```erlang 4 | -moduel(a). 5 | -export([do/0, go/0]). 6 | 7 | do_test() -> 8 | dummy. 9 | 10 | do() -> 11 | dummy. 12 | 13 | go_test() -> 14 | dummy. 15 | 16 | go() -> 17 | dummy. 18 | 19 | 20 | ``` 21 | 22 | You can run a specific test function just like any other function: 23 | ```erlang 24 | 1> c(a). 25 | {ok,a} 26 | 27 | 2> a:do_test(). 28 | dummy 29 | ``` 30 | With **eunit**, you can run both `do_test()` and `go_test()` with one command: 31 | 32 | ```erlang 33 | 4> c(a). 34 | {ok,a} 35 | 36 | 5> a:test(). 37 | All 2 tests passed. 38 | ok 39 | ``` 40 | You can enable **eunit** by adding the following line to the top of your file: 41 | 42 | -include_lib("eunit/include/eunit.hrl"). 43 | 44 | Then make sure that the names of your test functions end in `_test` (eunit automatically exports the test functions for you). The ability of eunit to run all the test functions in a file with one command is handy for solving this exercise. 45 | 46 | The book uses the old "suffix rules" in the makefile, which have been replaced by "pattern rules". I put a modern interpretation of a basic makefile alongside this file in the Chapter 10 directory. It is heavily commented with explanations. Here is that makefile modified to run all the test functions in all the modules in your project: 47 | 48 | ```makefile 49 | modules = a b #Assign all the names of your modules to a variable. 50 | 51 | .PHONY: all 52 | all: $(modules:%=%.beam) test #Added an additonal prerequisite file named test. 53 | #make needs to look further down the makefile to 54 | #figure out how to create the test file. 55 | %.beam: %.erl 56 | erlc -W $< 57 | 58 | .PHONY: test 59 | test: $(modules:%=%_test) #This tells make how to create the test file: make needs 60 | #to create the prerequisite files a_test and b_test. 61 | 62 | %_test: #This tells make how to create a *_test file. 63 | erl -noshell -s $* test -s init stop # $* is the part of the file name matched by the % wildcard 64 | 65 | .PHONY: clean 66 | clean: 67 | rm $(modules:%=%.beam) erl_crash.dump 68 | 69 | ``` 70 | 71 | On the command line: 72 | ``` 73 | ~/erlang_programs/ex10_1$ gmake 74 | erlc -W a.erl 75 | erlc -W b.erl 76 | erl -noshell -s a test -s init stop 77 | All 2 tests passed. 78 | erl -noshell -s b test -s init stop 79 | All 2 tests passed. 80 | ``` 81 | Note that make shows you the commands it's executing as well as the stdout of those commands. 82 | 83 | I just learned about makefiles while reading Chapter 10. If you are familiar with C/C++: 84 | 85 | http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/ 86 | 87 | Other really excellent makefile resources: 88 | 89 | http://www.oreilly.com/openbook/make3/book/ch02.pdf 90 | 91 | https://www.gnu.org/software/make/manual/make.html#Overview 92 | 93 | (Oh yeah, use gmake instead of make!) 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Chapter 10/modern_makefile: -------------------------------------------------------------------------------- 1 | #------------!WARNING!------------------ 2 | #makefiles require an actual tab character for the indenting. 3 | #I use macvim as my text editor, and I have it configured 4 | #to convert tabs to spaces for all file types. To make sure 5 | #that doesn't happen to a makefile, I added the following to 6 | # ~/.vimrc: 7 | # 8 | # au FileType make setlocal noexpandtab tabstop=4 shiftwidth=4 softtabstop=0 9 | # 10 | #--------------------------------------- 11 | # 12 | #First the makefile without comments: 13 | 14 | 15 | modules = a w2 b 16 | 17 | all: $(modules:%=%.beam) 18 | .PHONY: all 19 | 20 | %.beam: %.erl 21 | erlc -W $< 22 | 23 | clean: 24 | rm $(modules:%=%.beam) erl_crash.dump 25 | .PHONY: clean 26 | 27 | 28 | #Now, with comments: 29 | 30 | #Assign all the modules in your project to a variable: 31 | modules = a w2 b 32 | 33 | #The following is the first "target" in this makefile, which 34 | #means that make will try to create the file "all" (and only 35 | #the minimum necessary to create the file "all") by 36 | #first creating the "prerequisite" files, which are listed to 37 | #the right of the colon after "all". The syntax used to the right 38 | #of the colon converts the names in the modules variable 39 | #above to names with a .beam extension. $() is used to 40 | #interpolate variables, and % is a wild card character. 41 | #In this case, 42 | # $(modules:%=%.beam) 43 | #is equivalent to: 44 | # a.beam w2.beam b.beam 45 | #The fragment 'modules:%' matches each word in the modules variable, 46 | #and the fragment '=%.beam' replaces each match on the left hand side 47 | #of the equals sign with a new word %.beam, where % is replaced by 48 | #the same thing that matched % on the left hand side of the equals 49 | #sign. If instead you wrote, 50 | # $(modules:%.xx=%.beam), 51 | #then %.xx would match all the words in the modules variable that 52 | #ended in .xx, e.g. myfile.xx, and on the right hand side of the 53 | #equals sign, % would equal myfile, so the replacement would be 54 | #myfile.beam. 55 | 56 | all: $(modules:%=%.beam) 57 | .PHONY: all 58 | 59 | #In order for make to create the file "all", 60 | #make must figure out how to create the prerequisites, 61 | #so make looks further down the makefile to figure 62 | #out how to create the files a.beam, w2.beam, 63 | #and b.beam. 64 | 65 | #Ah, below is a target which instructs make on how it 66 | #can create any .beam file: make should take the 67 | #correspondingly named .erl file. and apply the 68 | #"recipe" on the second line to the .erl file to create 69 | #the .beam file. In the recipe, the .erl file is referred 70 | #to with the variable name: 71 | # $< 72 | #It represents the full file name of the file that matched 73 | #the pattern on the right side of the colon. 74 | #Yep, it's scary looking syntax. Welcome to perl! 75 | #($rhs would have been a better choice in my opinion!). 76 | #The -W option is for warnings. 77 | 78 | %.beam: %.erl 79 | erlc -W $< 80 | 81 | #To execute the "clean" target below, you need to use 82 | #this command: 83 | # 84 | # $ gmake clean 85 | # 86 | #Because creating the "clean" target is not required 87 | #to create the first target in the file, which is "all" 88 | #in this makefile, gmake/make skips "clean" when you do: 89 | # 90 | # $ gmake 91 | # 92 | #To create the "clean" target, you need to specify "clean" 93 | #when you call gmake/make, then gmake/make tries to create 94 | #only the "clean" target and its prerequisites. The 95 | #clean target lacks any prerequisites, so make just 96 | #executes the recipe. 97 | 98 | clean: 99 | rm $(modules:%=%.beam) erl_crash.dump 100 | .PHONY: clean 101 | 102 | #Note that both "all" and "clean" are phony targets because 103 | #none of the recipes will actually create a file called "all" 104 | #or "clean" (which means that make really feels like it failed 105 | #you!). It's considered good practice to declare your phony 106 | #targets with .PHONY: -- for reasons I won't go into. 107 | -------------------------------------------------------------------------------- /Chapter 12/exercise 1.md: -------------------------------------------------------------------------------- 1 | I feel like there should be a trickier solution than my solution, maybe using a server like process to handle the `register()`'ing. I envision `start/2` sending a message to the server process to `register()` a name, which would allow the first message in the mailbox to win when two processes try to register the same name. But if `start()` is executing in two different processes, I don't know how to connect those function calls to a central server process. I guess I could register the name `server`, then both processes could send a message to `server`--but that would require that the server process be started prior to calling `start/2`, which I don't think is a very convenient solution. 2 | 3 | As a result, my solution uses the VM as the central process, and I call `registered()` to discover what names have previously been registered. However, I am unsure if my solution ***guarantees that one process succeeds and one process fails***, and because I am unable to reason about that I don't believe my solution is correct! 4 | 5 | 6 | ```erlang 7 | -module(one). 8 | -export([start/2]). 9 | -include_lib("eunit/include/eunit.hrl"). 10 | 11 | start_test() -> 12 | Fun = fun() -> wait() end, 13 | 14 | true = start(hello, Fun), 15 | {error, {name_taken, hello}} = start(hello, Fun), 16 | 17 | %The following works: 18 | %unregister(hello), 19 | %true = start(hello, Fun), 20 | 21 | hello ! stop, 22 | receive after 10 -> true end, %Pause here required for next line to work 23 | true = start(hello, Fun), 24 | 25 | all_tests_passed. 26 | 27 | 28 | start(Name, Fun) -> 29 | case member(Name, registered() ) of 30 | false -> 31 | register(Name, spawn(Fun) ); 32 | true -> 33 | {error, {name_taken, Name}} 34 | end. 35 | 36 | %---------- 37 | 38 | wait() -> 39 | receive 40 | stop -> void 41 | end. 42 | 43 | %----------- 44 | 45 | member_test() -> 46 | false = member(1, []), 47 | true = member(a, [a,b]), 48 | false = member(x, [a,b,c]), 49 | true = member(x, [a,b,x]), 50 | all_tests_passed. 51 | 52 | member(_, []) -> 53 | false; 54 | member(X, [X|_]) -> 55 | true; 56 | member(X, [_|Ys]) -> 57 | member(X, Ys). 58 | 59 | ``` 60 | ------ 61 | Okay, to settle the unease I felt with my solution, I did some searching around. First of all, I overlooked the `whereis()` function listed on p. 195 (alongside the register functions), so here is my code refactored to use `whereis()`: 62 | ```erlang 63 | start(Atom, Fun) -> 64 | case whereis(Atom) of %whereis() is listed on p. 195 along with register(). 65 | undefined -> 66 | register(Atom, spawn(Fun) ); 67 | _ -> 68 | {error, {name_taken, Atom}} 69 | end. 70 | ``` 71 | Secondly, I think the question is mistated because if two processes simultaneously call `start/2` one of them *will* fail. Rather, the question should require that one process be guaranteed to *spawn* the Fun and the other process be guaranteed *not to spawn* the Fun. 72 | 73 | With my code, it's possible for two processes to simultaneously execute `start/2` and each spawn their own Fun. When two processes execute at the same time, it's possible for a line in one process to execute, then a line in the other process to execute. So, what happens if execution proceeds in this order: 74 | 75 | process1: case whereis(hello) of 76 | process2: case whereis(hello) of 77 | 78 | In the first line, `whereis()` will return `undefined`, meaning that no process has been registered with the name hello (that's assuming that some other process hasn't already registered the name hello). Thereafter, execution could switch to process2 and `whereis()` will return `undefined` again. As a result, both processes will attempt to call register(): 79 | 80 | case whereis(Atom) of 81 | undefined -> 82 | register(Atom, spawn(Fun) ); 83 | 84 | 85 | process1: register(hello, spawn(Fun) ); 86 | process2: register(hello, spawn(Fun) ); 87 | 88 | Let's assume that process1 wins and `register()`'s the name hello. Next, when process2 calls `register()`, any expressions in the argument list have to be evaluated first, so `spawn(Fun)` will execute and return a pid. Then `register()` will execute and throw an exception because process1 already took the name hello, which will kill process2. But after process2 dies, the process spawned by process2 will still be alive and running. As a result, both processes will spawn a new process--one of the spawned process will be named hello and the other spawned process will not have a name. 89 | 90 | To guarantee that only one process is able to spawn a function, the fix is: 91 | 92 | ```erlang 93 | start(Name, Fun) -> 94 | spawn(fun() -> 95 | register(Name, Fun), 96 | Fun() 97 | end). 98 | ``` 99 | 100 | With that code, if register() throws an exception then the spawned process will fail. The difference between my solution and the fix is that my start/2 function first checks if the name is registered, then spawns a fun, while the fix first spawns a fun and inside the spawned fun the fix tries to register the name. 101 | 102 | I found the fix here: 103 | 104 | http://erlang.org/pipermail/erlang-questions/2007-July/028139.html 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /Chapter 12/exercise 2.md: -------------------------------------------------------------------------------- 1 | ![graph of runtimes v. processes](https://github.com/7stud/Programming-Erlang-Exercises-Solutions-Answers/blob/master/Chapter%2012/runtimes.png) 2 | 3 | ### *What can you deduce from the graph?* 4 | You can create a lot of processes with erlang! It looks like the walltime goes up faster than the runtime, so the CPU must be idle for longer periods of time when the erlang VM creates a large number of processes. 5 | -------------------------------------------------------------------------------- /Chapter 12/exercise 3.md: -------------------------------------------------------------------------------- 1 | I thought the ring exercise was very difficult. I wrote five or six different solutions, and every time my program would hang somewhere. Finally, I got something to work--but it seems overly complex. I created a separate receive loop for the "start" process in order to decrement the loop count in addition to the receive loop for the other processes. I also passed the pid of the next process as an argument to the receive loops to keep the pid from going out of scope. 2 | 3 | ```erlang 4 | -module(ring3). 5 | -export([ring/2]). 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | ring(NumProcs, NumLoops) -> 9 | StartPid = self(), 10 | NextPid = spawn(fun() -> create_ring(NumProcs-1, StartPid) end), 11 | NextPid ! {NumLoops, "hello"}, 12 | ring_start(NextPid). %receive loop for the "start" process. 13 | 14 | create_ring(1, StartPid) -> %...then stop spawning processes. 15 | loop(StartPid); %receive loop for the other processes. 16 | create_ring(NumProcs, StartPid) -> 17 | NextPid = spawn(fun() -> create_ring(NumProcs-1, StartPid) end), 18 | loop(NextPid). 19 | 20 | %receive loop for the "start" process: 21 | ring_start(NextPid) -> 22 | receive 23 | {1, Msg} -> %...then stop looping. 24 | io:format("**ring_start ~w received: ~s (~w)~n", [self(), Msg, 1]), 25 | NextPid ! stop; %kill other processes; this processs will die because it stops looping. 26 | {NumLoops, Msg} -> 27 | io:format("**ring_start ~w received: ~s (~w)~n", [self(), Msg, NumLoops]), 28 | NextPid ! {NumLoops-1, Msg}, 29 | ring_start(NextPid) 30 | end. 31 | 32 | %receive loop for the other processes: 33 | loop(NextPid) -> 34 | receive 35 | {NumLoops, Msg} -> 36 | io:format("Process ~w received message: ~s (~w)~n", [self(), Msg, NumLoops]), 37 | NextPid ! {NumLoops, Msg}, 38 | loop(NextPid); 39 | stop -> 40 | NextPid ! stop % When sent to non-existent "start" process, this still returns stop. 41 | end. 42 | 43 | ``` 44 | 45 | In the shell: 46 | 47 | ``` 48 | 15> c(ring3). 49 | {ok,ring3} 50 | 51 | 16> ring3:ring(5,4). 52 | Process <0.140.0> received message: hello (4) 53 | Process <0.141.0> received message: hello (4) 54 | Process <0.142.0> received message: hello (4) 55 | Process <0.143.0> received message: hello (4) 56 | **start <0.32.0> received: hello (4) 57 | Process <0.140.0> received message: hello (3) 58 | Process <0.141.0> received message: hello (3) 59 | Process <0.142.0> received message: hello (3) 60 | Process <0.143.0> received message: hello (3) 61 | **start <0.32.0> received: hello (3) 62 | Process <0.140.0> received message: hello (2) 63 | Process <0.141.0> received message: hello (2) 64 | Process <0.142.0> received message: hello (2) 65 | Process <0.143.0> received message: hello (2) 66 | **start <0.32.0> received: hello (2) 67 | Process <0.140.0> received message: hello (1) 68 | Process <0.141.0> received message: hello (1) 69 | Process <0.142.0> received message: hello (1) 70 | Process <0.143.0> received message: hello (1) 71 | **start <0.32.0> received: hello (1) 72 | stop 73 | 74 | 17> i(). 75 | ... 76 | ... 77 | <0.25.0> group:server/3 2586 40094 0 78 | group:server_loop/3 4 79 | <0.26.0> erlang:apply/2 17731 6223 0 80 | shell:shell_rep/4 17 81 | <0.27.0> kernel_config:init/1 233 2322 0 82 | gen_server:loop/6 9 83 | <0.28.0> supervisor:kernel/1 233 2150 0 84 | kernel_safe_sup gen_server:loop/6 9 85 | <0.32.0> erlang:apply/2 610 31978 8 86 | c:pinfo/1 50 87 | Total 49206 1005265 8 88 | 219 89 | ok 90 | 91 | 18> ring3:ring(3, 7). 92 | Process <0.146.0> received message: hello (7) 93 | Process <0.147.0> received message: hello (7) 94 | **start <0.32.0> received: hello (7) 95 | Process <0.146.0> received message: hello (6) 96 | Process <0.147.0> received message: hello (6) 97 | **start <0.32.0> received: hello (6) 98 | Process <0.146.0> received message: hello (5) 99 | Process <0.147.0> received message: hello (5) 100 | **start <0.32.0> received: hello (5) 101 | Process <0.146.0> received message: hello (4) 102 | Process <0.147.0> received message: hello (4) 103 | **start <0.32.0> received: hello (4) 104 | Process <0.146.0> received message: hello (3) 105 | Process <0.147.0> received message: hello (3) 106 | **start <0.32.0> received: hello (3) 107 | Process <0.146.0> received message: hello (2) 108 | Process <0.147.0> received message: hello (2) 109 | **start <0.32.0> received: hello (2) 110 | Process <0.146.0> received message: hello (1) 111 | Process <0.147.0> received message: hello (1) 112 | **start <0.32.0> received: hello (1) 113 | stop 114 | 115 | 19> i(). 116 | ... 117 | ... 118 | <0.25.0> group:server/3 2586 58135 0 119 | group:server_loop/3 4 120 | <0.26.0> erlang:apply/2 17731 6407 0 121 | shell:shell_rep/4 17 122 | <0.27.0> kernel_config:init/1 233 2322 0 123 | gen_server:loop/6 9 124 | <0.28.0> supervisor:kernel/1 233 2150 0 125 | kernel_safe_sup gen_server:loop/6 9 126 | <0.32.0> erlang:apply/2 4185 51215 9 127 | c:pinfo/1 50 128 | Total 51793 1046386 9 129 | 219 130 | ok 131 | 132 | ``` 133 | 134 | ### *Benchmarks*: 135 | 136 | I commented out the io:format() statements then benchmarked the code: 137 | ```erlang 138 | -module(ring4). 139 | -export([ring/2]). 140 | -include_lib("eunit/include/eunit.hrl"). 141 | 142 | ring(NumProcs, NumLoops) -> 143 | 144 | statistics(runtime), 145 | statistics(wall_clock), 146 | 147 | StartPid = self(), 148 | NextPid = spawn(fun() -> create_ring(NumProcs-1, StartPid) end), 149 | NextPid ! {NumLoops, "hello"}, 150 | start(NextPid), %receive loop for this process, i.e. the "start" process 151 | 152 | {_,RunTime} = statistics(runtime), 153 | {_,WallTime} = statistics(wall_clock), 154 | io:format("RunTime: ~w, WallTime: ~w (~w, ~w)~n", 155 | [RunTime,WallTime,NumProcs,NumLoops] 156 | ). 157 | 158 | create_ring(1, StartPid) -> %...then stop spawning processes. 159 | loop(StartPid); %receive loop for the other processes. 160 | create_ring(NumProcs, StartPid) -> 161 | NextPid = spawn(fun() -> create_ring(NumProcs-1, StartPid) end), 162 | loop(NextPid). 163 | 164 | %receive loop for the "start" process: 165 | start(NextPid) -> 166 | receive 167 | {1, _Msg} -> %...then stop looping. 168 | %io:format("**start ~w received: ~s (~w)~n", [self(), Msg, 1]), 169 | NextPid ! stop; %kill other processes; this processs will die because it stops looping. 170 | 171 | {NumLoops, Msg} -> 172 | %io:format("**start ~w received: ~s (~w)~n", [self(), Msg, NumLoops]), 173 | NextPid ! {NumLoops-1, Msg}, 174 | start(NextPid) 175 | end. 176 | 177 | %receive loop for the other processes: 178 | loop(NextPid) -> 179 | receive 180 | {NumLoops, Msg} -> 181 | %io:format("Process ~w received message: ~s (~w)~n", [self(), Msg, NumLoops]), 182 | NextPid ! {NumLoops, Msg}, 183 | loop(NextPid); 184 | 185 | stop -> 186 | NextPid ! stop % When sent to non-existent "start" process, this still returns stop. 187 | end. 188 | 189 | ``` 190 | 191 | In the shell: 192 | 193 | ``` 194 | 10> c(ring4). 195 | {ok,ring4} 196 | 197 | 11> ring4:ring(1000, 500). 198 | RunTime: 290, WallTime: 330 (1000, 500) 199 | ok 200 | 12> ring4:ring(500, 1000). 201 | RunTime: 290, WallTime: 314 (500, 1000) 202 | ok 203 | 13> ring4:ring(2000, 1000). 204 | RunTime: 1310, WallTime: 1436 (2000, 1000) 205 | ok 206 | 14> ring4:ring(1000, 2000). 207 | RunTime: 1160, WallTime: 1276 (1000, 2000) 208 | ok 209 | 15> ring4:ring(4000, 2000). 210 | RunTime: 5730, WallTime: 6199 (4000, 2000) 211 | ok 212 | 16> ring4:ring(2000, 4000). 213 | RunTime: 5270, WallTime: 5710 (2000, 4000) 214 | ok 215 | 17> ring4:ring(8000, 1000). 216 | RunTime: 5880, WallTime: 6431 (8000, 1000) 217 | ok 218 | 18> ring4:ring(1000, 8000). 219 | RunTime: 4490, WallTime: 4901 (1000, 8000) 220 | ok 221 | 19> ring4:ring(12000, 1000). 222 | RunTime: 8790, WallTime: 9596 (12000, 1000) 223 | ok 224 | 20> ring4:ring(1000, 12000). 225 | RunTime: 6790, WallTime: 7419 (1000, 12000) 226 | ok 227 | ``` 228 | 229 | It looks like creating fewer processes and running more loops is faster. 230 | -------------------------------------------------------------------------------- /Chapter 12/runtimes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7stud/Programming-Erlang-Exercises-Solutions-Answers/ac20cdbef436c68b1c410ba44009f90ae9a87e7b/Chapter 12/runtimes.png -------------------------------------------------------------------------------- /Chapter 13/_running_programs.md: -------------------------------------------------------------------------------- 1 | I am sick of typing: 2 | 3 | ``` 4 | $ erl 5 | 6 | 1> c(e1). 7 | 8 | 2> e1:test(). 9 | 10 | ``` 11 | over and over again. I shortened my module names to two letters, which makes things easier, but it's still a pain. So I created a shell script in the same directory called `run`: 12 | 13 | ``` 14 | #!/usr/bin/env bash 15 | 16 | erlc -W e1.erl 17 | erl -s e1 test 18 | ``` 19 | 20 | (You are not supposed to use extensions for shell script file names, e.g. run.sh, but using a .sh/.bash extension will allow my editor's syntax highlighting to take effect.) 21 | 22 | Then I made it executable: 23 | ``` 24 | $ chmod u+x run 25 | ``` 26 | Now, I can run my elrang program from the command line like this: 27 | ``` 28 | $ ./run 29 | ``` 30 | When I need to run my program again, I kill the erlang shell with `Ctrl-cc`, then I can just hit the up arrow on my keyboard, which will scroll to the previous command, then I hit Return. No more typing in the shell! 31 | 32 | Potential problems when using a shell script: 33 | 34 | 1. Sometimes after running my shell script on a particular program, the shell is unresponsive, so I can't check `i()` or run `observer:start()`. If the shell is unresponsive after I run my shell script, and I need the shell to be responsive, then I create a function called `test_init()`, which just spawns my `test()` function; then I substitute the name `test_init` in place of `test` in my shell script. 35 | 36 | 2. The program output will interleave with the output that the shell displays on startup. To keep things tidy, I begin my `test()` functions with a short `timer:sleep(500)`. That way the output from the erlang shell initialization has time to print before my erlang program begins outputting text. 37 | 38 | 3. Sometimes the last line of output won't be the same as in the erlang shell, so I have to resort to using the erlang shell again to confirm that everything is working correctly. A shell script runs in a different process than the erlang shell, and the script process is not in a read-evaluate loop like the erlang shell, so the script process won't automaticaly output the result of the expression on the last line of your program. Of course, you can just call `io:format()` to print out the value of that last expression. 39 | 40 | Despite those three issues, I still find that using a shell script to run an erlang program is a _massive_ time saver, and being able to rerun your program quickly by hitting the up arrow, then hitting Return will keep you from going insane during long debugging sessions. 41 | 42 | -------------------------------------------------------------------------------- /Chapter 13/exercise 1.md: -------------------------------------------------------------------------------- 1 | My first attempt is below. 2 | ```erlang 3 | -module(e1). 4 | -export([my_spawn/3, atomize/0, test/0]). 5 | 6 | my_spawn(Mod, Func, Args) -> 7 | %%Create separate process to run Func: 8 | FuncPid = spawn(Mod, Func, Args), 9 | statistics(wall_clock), %%Get the start time (and throw away the return value). 10 | 11 | %%****WHAT IF THE FuncPid PROCESS FAILS HERE???****** 12 | 13 | %%Create separate process for the monitor: 14 | spawn(fun() -> 15 | Ref = monitor(process, FuncPid), %%Ref is sent in the 'DOWN' message. 16 | receive 17 | {'DOWN', Ref, process, FuncPid, Why} -> %% Ref and FuncPid are bound! 18 | {_, WallTime} = statistics(wall_clock), %%Get the elapsed since the last call to statistics(wall_clock). 19 | io:format("Process ~w lived for ~w milliseconds,~n", 20 | [FuncPid, WallTime]), 21 | io:format("then died due to: ~p~n", [Why]), 22 | io:format("*---------*~n") 23 | end 24 | end), %%Monitor process dies after receiving a 'DOWN' message. 25 | 26 | FuncPid. %%To mimic spawn(Mod, Func, Args), return the Pid of the 27 | %%process that is running Func. 28 | 29 | atomize() -> 30 | receive 31 | List -> list_to_atom(List) 32 | end. 33 | 34 | test() -> 35 | timer:sleep(500), %%Allow time for shell startup 36 | %%so output appears after 1> prompt. 37 | io:format("testing...~n"), 38 | 39 | Atomizer = my_spawn(e1, atomize, []), 40 | timer:sleep(2000), %%Let atomize() run for awhile. 41 | Atomizer ! hello, %%Will cause an error in Atomizer. 42 | ok. 43 | 44 | ``` 45 | 46 | In the shell: 47 | 48 | ``` 49 | $ ./run 50 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 51 | 52 | Eshell V8.2 (abort with ^G) 53 | 54 | 1> testing... 55 | Process <0.59.0> lived for 2001 milliseconds, 56 | then died due to: {badarg,[{erlang,list_to_atom,[hello],[]}, 57 | {e1,atomize,0,[{file,"e1.erl"},{line,27}]}]} 58 | 59 | =ERROR REPORT==== 7-May-2017::15:23:31 === 60 | Error in process <0.59.0> with exit value: 61 | {badarg,[{erlang,list_to_atom,[hello],[]}, 62 | {e1,atomize,0,[{file,"e1.erl"},{line,27}]}]} 63 | *---------* 64 | 65 | ``` 66 | 67 | That solution has a serious problem though: if the FuncPid process fails immediately after being spawned, the monitor will not be hooked up yet, so the information about the process life will not be output. (**Edit:** Nope! A monitor will still work if the process doesn't exist when the monitor is created: the monitor will receive a 'DOWN' message and `Why`will be `noproc`, meaning _no process_). I think the following attempt covers the problems with my first solution: 68 | ```erlang 69 | -module(e1). 70 | -export([my_spawn/3, atomize/0, test/0]). 71 | 72 | my_spawn(Mod, Func, Args) -> 73 | MySpawn = self(), 74 | Tag = make_ref(), %%Tag needs to be accesible in the spawned func as well 75 | %%as in the receive at the end of this func. 76 | %%Create separate process for the monitor: 77 | Monitor = 78 | spawn(fun() -> 79 | {FuncPid, Ref} = spawn_monitor(Mod, Func, Args), %%Erlang will include Ref in the 'DOWN' message 80 | statistics(wall_clock), 81 | MySpawn ! {self(), Tag, FuncPid}, %%Use the Monitor pid and a reference to tag the message. 82 | receive 83 | {'DOWN', Ref, process, FuncPid, Why} -> %% Ref and FuncPid are bound! 84 | {_, WallTime} = statistics(wall_clock), %%Elapsed time since last call. 85 | io:format("Process ~w lived for ~w milliseconds,~n", 86 | [FuncPid, WallTime]), 87 | io:format("then died due to: ~p~n", [Why]), 88 | io:format("*---------*~n") 89 | end 90 | end), %%Monitor process dies after receiving a 'DOWN' message. 91 | 92 | receive %%Blocks until the line that sends the message in Monitor executes. 93 | {Monitor, Tag, FuncPid} -> FuncPid 94 | end. 95 | 96 | 97 | atomize() -> 98 | receive 99 | List -> list_to_atom(List) 100 | end. 101 | 102 | test() -> 103 | timer:sleep(500), %%Allow time for shell startup 104 | %%so output appears after 1> prompt. 105 | io:format("testing...~n"), 106 | 107 | Atomizer = my_spawn(e1, atomize, []), 108 | timer:sleep(2000), %%Let atomize() run for awhile. 109 | Atomizer ! hello, 110 | ok. 111 | 112 | ``` 113 | 114 | In the shell: 115 | ``` 116 | $ ./run 117 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 118 | 119 | Eshell V8.2 (abort with ^G) 120 | 1> testing... 121 | Process <0.60.0> lived for 2001 milliseconds, 122 | then died due to: {badarg,[{erlang,list_to_atom,[hello],[]}, 123 | {e1,atomize,0,[{file,"e1.erl"},{line,31}]}]} 124 | 125 | =ERROR REPORT==== 8-May-2017::09:29:58 === 126 | Error in process <0.60.0> with exit value: 127 | {badarg,[{erlang,list_to_atom,[hello],[]}, 128 | {e1,atomize,0,[{file,"e1.erl"},{line,31}]}]} 129 | *---------* 130 | 131 | ``` 132 | -------------------------------------------------------------------------------- /Chapter 13/exercise 2.md: -------------------------------------------------------------------------------- 1 | Using `on_exit()` in `my_spawn/3`: 2 | 3 | ```erlang 4 | -module(my). 5 | -export([atomizer/0, my_spawn/3, test/0]). 6 | 7 | on_exit(Pid, Fun) -> 8 | spawn( fun() -> 9 | Ref = monitor(process, Pid), 10 | receive 11 | {'DOWN', Ref, process, Pid, Why} -> 12 | Fun(Why); 13 | Other -> io:format("Other: ~w~n", [Other]) 14 | end 15 | end). 16 | 17 | my_spawn(Mod, Fun, Args) -> 18 | Pid = spawn(Mod, Fun, Args), 19 | statistics(wall_clock), %%Get the elapsed time since the previous call 20 | %%to statistics(wall_clock) and throw it away. 21 | 22 | %%exit(Pid, kill), 23 | 24 | TerminationFun = 25 | fun(Why) -> 26 | {_, WallTime} = statistics(wall_clock), %%Get the elapsed time since the 27 | %%previous call to statistics(wall_clock). 28 | io:format("Process (~w) terminated. ", [Pid]), 29 | io:format("It lived for ~w milliseconds.~n", [WallTime]), 30 | io:format("Then it died due to: ~p~n", [Why]) 31 | end, 32 | 33 | on_exit(Pid, TerminationFun), %%Returns Pid of monitor. 34 | Pid. %%Need to return Pid of function being monitored. 35 | 36 | atomize() -> 37 | receive 38 | List -> list_to_atom(List) 39 | end. 40 | 41 | test() -> 42 | timer:sleep(500), %%Allow time for shell to startup. 43 | io:format("testing...~n"), 44 | 45 | Pid = my_spawn(my, atomize, []), 46 | timer:sleep(2000), %%Allow atomize() to run for awhile. 47 | Pid ! hello. %%Causes error in Pid. 48 | 49 | 50 | ``` 51 | 52 | In the shell: 53 | ``` 54 | $ ./run 55 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false 56 | 57 | Eshell V8.2 (abort with ^G) 58 | 59 | 1> testing... 60 | Process (<0.59.0>) terminated. It lived for 2001 milliseconds. 61 | Then it died due to: {badarg,[{erlang,list_to_atom,[hello],[]}, 62 | {e2,atomize,0,[{file,"e2.erl"},{line,32}]}]} 63 | 64 | =ERROR REPORT==== 7-May-2017::15:16:44 === 65 | Error in process <0.59.0> with exit value: 66 | {badarg,[{erlang,list_to_atom,[hello],[]}, 67 | {e2,atomize,0,[{file,"e2.erl"},{line,32}]}]} 68 | ``` 69 | 70 | Once again, there's a serious problem with the solution: if the process running the funtion dies before the monitor can be created, then the timing info won't be printed out. If we are forced to use `on_ext()`, which requires a Pid as an argument, then we have to create the process first, which means there will always be a chance that the process will die before the monitor can be established. (**Edit:** Nope! A monitor will still work if the process doesn't exist when the monitor is created: the monitor will receive a 'DOWN' message and `Why` will be `noproc`, meaning _no process_). 71 | -------------------------------------------------------------------------------- /Chapter 13/exercise 3.md: -------------------------------------------------------------------------------- 1 | ```erlang 2 | -module(e3). 3 | -export([my_spawn/4, killer/2, test/0, loop/1]). 4 | 5 | killer(Pid, Timeout) -> 6 | receive 7 | after Timeout -> 8 | io:format("killer(): ~w sent 'kill' signal after ~w milliseconds.~n", 9 | [Pid, Timeout]), 10 | exit(Pid, kill) 11 | end. 12 | 13 | my_spawn(Mod, Func, Args, Timeout) -> 14 | Pid = spawn(Mod, Func, Args), 15 | %%exit(Pid, testing), 16 | io:format("~w:~w(~w) running in process: ~w~n", [Mod, Func, Args, Pid]), 17 | spawn(?MODULE, killer, [Pid, Timeout]), 18 | Pid. 19 | 20 | %------------- 21 | 22 | test() -> 23 | timer:sleep(500), %%Allow time for the output from the shell startup to be printed. 24 | my_spawn(?MODULE, loop, [1], 7500), 25 | testing. 26 | 27 | loop(N) -> 28 | receive 29 | after 1000 -> 30 | io:format("loop(): tick ~w~n", [N]), 31 | loop(N+1) 32 | end. 33 | 34 | ``` 35 | Note that if `Func` is trapping exits, then the process that `Func` is running in won't die unless you send the process a 'kill' signal. 36 | 37 | In the shell, 38 | ``` 39 | $ ./run 40 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 41 | Eshell V8.2 (abort with ^G) 42 | 43 | 1> e3:loop([1]) running in process: <0.59.0> 44 | loop(): tick 1 45 | loop(): tick 2 46 | loop(): tick 3 47 | loop(): tick 4 48 | loop(): tick 5 49 | loop(): tick 6 50 | loop(): tick 7 51 | killer(): <0.59.0> sent 'kill' signal after 7500 milliseconds. 52 | ``` 53 | -------------------------------------------------------------------------------- /Chapter 13/exercise 4.md: -------------------------------------------------------------------------------- 1 | I thought this exercise was pretty difficult. At some point, I abandoned the verbatim description in the exercise, and I just tried to come up with any solution. After I figured out a solution, I added the user interface: `start()`, `stop()`. 2 | 3 | ```erlang 4 | -module(e4). 5 | %%-compile(export_all). 6 | -export([func_init/0, func/0, monitor_init/0, my_monitor/0]). 7 | -export([start/0, stop/0]). 8 | -include_lib("eunit/include/eunit.hrl"). 9 | 10 | func_init() -> 11 | register(?MODULE, Pid = spawn(e4, func, []) ), 12 | Pid. 13 | 14 | func() -> 15 | receive 16 | stop -> ok 17 | after 1000 -> 18 | io:format("I'm still running.~n"), 19 | func() 20 | end. 21 | 22 | %%------------- 23 | 24 | monitor_init() -> 25 | register(monitor, spawn(?MODULE, my_monitor, [])). 26 | 27 | my_monitor() -> 28 | Func = func_init(), 29 | io:format("Func is running in process: ~w~n", [Func]), 30 | Ref = monitor(process, Func), 31 | 32 | receive 33 | {'DOWN', Ref, process, Func, Why} -> 34 | io:format("~w went down: ~w~nrestarting...~n", [Func, Why]), 35 | my_monitor(); 36 | {request, stop} -> 37 | Func ! stop 38 | end. 39 | 40 | %%----------- 41 | 42 | start() -> 43 | monitor_init(). 44 | 45 | stop() -> 46 | monitor ! {request, stop}, 47 | shutdown. 48 | 49 | monitor_test() -> 50 | start(), 51 | 52 | timer:sleep(3200), 53 | exit(whereis(e4), my_stop), 54 | timer:sleep(4200), 55 | exit(whereis(e4), kill), 56 | timer:sleep(2200), 57 | 58 | stop(). 59 | ``` 60 | 61 | In the shell: 62 | 63 | ``` 64 | $ erl 65 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 66 | Eshell V8.2 (abort with ^G) 67 | 68 | 1> c(e4). 69 | {ok,e4} 70 | 71 | 2> e4:monitor_test(). 72 | Func is running in process: <0.44.0> 73 | I'm still running. 74 | I'm still running. 75 | I'm still running. 76 | <0.44.0> went down: my_stop 77 | restarting... 78 | Func is running in process: <0.45.0> 79 | I'm still running. 80 | I'm still running. 81 | I'm still running. 82 | I'm still running. 83 | <0.45.0> went down: killed 84 | restarting... 85 | Func is running in process: <0.46.0> 86 | I'm still running. 87 | I'm still running. 88 | shutdown 89 | 90 | 3> 91 | 92 | 93 | 94 | 95 | ``` 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /Chapter 13/exercise 5.md: -------------------------------------------------------------------------------- 1 | Debugging hell! 2 | 3 | 1. I kept a list of the monitored Workers, and each monitored Worker has the following strucuture: 4 | 5 | { {Pid, Ref}, Func} 6 | 7 | `{Pid, Ref}` is returned by `spawn_monitor(Func)`. 8 | 9 | 2. Originally, I had `restart_worker()` handle the case where the pid of the killed worker is not found in the list of monitored pids, i.e if `lists:keyfind()` returns false, but I don't think that's possible: it would mean that the monitor received a 'DOWN' message from a process that it wasn't monitoring, so I eliminated that case. 10 | 11 | 3. At the bottom, there is a version that uses maps in Erlang 19.2. 12 | 13 | 14 | ```erlang 15 | -module(e5). 16 | %%-compile(export_all). 17 | -export([monitor_workers_init/1, monitor_workers/1]). 18 | -export([stop_monitor/1, test/0]). 19 | 20 | monitor_workers_init(Funcs) -> 21 | spawn(?MODULE, monitor_workers, [Funcs]). 22 | 23 | monitor_workers(Funcs) -> 24 | Workers = [ {spawn_monitor(Func), Func} || Func <- Funcs], %% { {Pid, Ref}, Func} 25 | io:format("monitor_workers(): Workers: ~n~p~n", [Workers]), 26 | monitor_workers_loop(Workers). 27 | 28 | monitor_workers_loop(Workers) -> 29 | receive 30 | {'DOWN', Ref, process, Pid, Why} -> 31 | io:format("monitor_workers_loop(): Worker ~w went down: ~w~n.", [{Pid, Ref}, Why]), 32 | NewWorkers = restart_worker({Pid, Ref}, Workers), 33 | monitor_workers_loop(NewWorkers); 34 | {request, stop} -> 35 | lists:foreach(fun({{Pid,_},_}) -> Pid ! stop end, 36 | Workers), %% { {Pid, Ref}, Func} 37 | io:format("\tMonitor sent stop messages to all workers.~n"), 38 | io:format("\tMonitor terminating: normal.~n"); 39 | 40 | {request, kill_worker, _From} -> %%For testing. 41 | kill_rand_worker(Workers), 42 | monitor_workers_loop(Workers) 43 | end. 44 | 45 | restart_worker(PidRef, Workers) -> %%Monitor calls this function in response to a 'DOWN' message. 46 | {_, Func} = lists:keyfind(PidRef, 1, Workers), %% { {Pid, Ref}, Func} 47 | NewPidRef = spawn_monitor(Func), 48 | io:format("...restarting ~w => ~w) ~n", [PidRef, NewPidRef]), 49 | 50 | NewWorkers = lists:keydelete(PidRef, 1, Workers), %% { {Pid, Ref}, Func} 51 | [{NewPidRef, Func} | NewWorkers]. 52 | 53 | kill_rand_worker(Workers) -> %%Monitor calls this function in response to a kill_worker request 54 | RandNum = rand:uniform(length(Workers) ), 55 | {{Pid, _}, _} = lists:nth(RandNum, Workers), %% { {Pid, Ref} Func} 56 | io:format("kill_rand_worker(): about to kill ~w~n", [Pid]), 57 | exit(Pid, kill). 58 | 59 | stop_monitor(Monitor) -> 60 | Monitor ! {request, stop}. 61 | 62 | %======== TESTS ========== 63 | 64 | worker(N) -> 65 | receive 66 | stop -> 67 | io:format("\tWorker~w stopping: normal.~n", [N]) 68 | after N*1000 -> 69 | io:format("Worker~w (~w) is still alive.~n", [N, self()] ), 70 | worker(N) 71 | end. 72 | 73 | test() -> 74 | timer:sleep(500), %%Allow output from startup of the erlang shell to print. 75 | 76 | Funcs = [fun() -> worker(N) end || N <- lists:seq(1, 4) ], 77 | Monitor = monitor_workers_init(Funcs), 78 | io:format("Monitor is: ~w~n", [Monitor]), 79 | 80 | timer:sleep(5200), %%Let monitored processes run for awhile. 81 | 82 | FiveTimes = 5, 83 | TimeBetweenKillings = 5200, 84 | kill_rand_worker(FiveTimes, TimeBetweenKillings, Monitor), 85 | 86 | stop_monitor(Monitor). 87 | 88 | kill_rand_worker(0, _, _) -> 89 | ok; 90 | kill_rand_worker(NumTimes, TimeBetweenKillings, Monitor) -> 91 | Monitor ! {request, kill_worker, self()}, 92 | timer:sleep(TimeBetweenKillings), 93 | kill_rand_worker(NumTimes-1, TimeBetweenKillings, Monitor). 94 | ``` 95 | In the shell: 96 | ``` 97 | $ ./run 98 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 99 | Eshell V8.2 (abort with ^G) 100 | 101 | 1> Monitor is: <0.59.0> 102 | monitor_workers(): Workers: 103 | [{{<0.60.0>,#Ref<0.0.3.132>},#Fun}, 104 | {{<0.61.0>,#Ref<0.0.3.133>},#Fun}, 105 | {{<0.62.0>,#Ref<0.0.3.134>},#Fun}, 106 | {{<0.63.0>,#Ref<0.0.3.135>},#Fun}] 107 | Worker1 (<0.60.0>) is still alive. 108 | Worker2 (<0.61.0>) is still alive. 109 | Worker1 (<0.60.0>) is still alive. 110 | Worker3 (<0.62.0>) is still alive. 111 | Worker1 (<0.60.0>) is still alive. 112 | Worker4 (<0.63.0>) is still alive. 113 | Worker2 (<0.61.0>) is still alive. 114 | Worker1 (<0.60.0>) is still alive. 115 | Worker1 (<0.60.0>) is still alive. 116 | kill_rand_worker(): about to kill <0.63.0> 117 | monitor_loop(): Worker {<0.63.0>,#Ref<0.0.3.135>} went down: killed 118 | ....restarting {<0.63.0>,#Ref<0.0.3.135>} => {<0.64.0>,#Ref<0.0.3.150>}) 119 | Worker3 (<0.62.0>) is still alive. 120 | Worker2 (<0.61.0>) is still alive. 121 | Worker1 (<0.60.0>) is still alive. 122 | Worker1 (<0.60.0>) is still alive. 123 | Worker2 (<0.61.0>) is still alive. 124 | Worker1 (<0.60.0>) is still alive. 125 | Worker3 (<0.62.0>) is still alive. 126 | Worker1 (<0.60.0>) is still alive. 127 | Worker4 (<0.64.0>) is still alive. 128 | Worker2 (<0.61.0>) is still alive. 129 | Worker1 (<0.60.0>) is still alive. 130 | kill_rand_worker(): about to kill <0.60.0> 131 | monitor_loop(): Worker {<0.60.0>,#Ref<0.0.3.132>} went down: killed 132 | ....restarting {<0.60.0>,#Ref<0.0.3.132>} => {<0.65.0>,#Ref<0.0.3.165>}) 133 | Worker1 (<0.65.0>) is still alive. 134 | Worker3 (<0.62.0>) is still alive. 135 | Worker2 (<0.61.0>) is still alive. 136 | Worker1 (<0.65.0>) is still alive. 137 | Worker4 (<0.64.0>) is still alive. 138 | Worker1 (<0.65.0>) is still alive. 139 | Worker2 (<0.61.0>) is still alive. 140 | Worker1 (<0.65.0>) is still alive. 141 | Worker3 (<0.62.0>) is still alive. 142 | Worker1 (<0.65.0>) is still alive. 143 | kill_rand_worker(): about to kill <0.62.0> 144 | monitor_loop(): Worker {<0.62.0>,#Ref<0.0.3.134>} went down: killed 145 | ....restarting {<0.62.0>,#Ref<0.0.3.134>} => {<0.66.0>,#Ref<0.0.3.179>}) 146 | Worker2 (<0.61.0>) is still alive. 147 | Worker1 (<0.65.0>) is still alive. 148 | Worker4 (<0.64.0>) is still alive. 149 | Worker1 (<0.65.0>) is still alive. 150 | Worker2 (<0.61.0>) is still alive. 151 | Worker1 (<0.65.0>) is still alive. 152 | Worker3 (<0.66.0>) is still alive. 153 | Worker1 (<0.65.0>) is still alive. 154 | Worker2 (<0.61.0>) is still alive. 155 | Worker1 (<0.65.0>) is still alive. 156 | kill_rand_worker(): about to kill <0.64.0> 157 | monitor_loop(): Worker {<0.64.0>,#Ref<0.0.3.150>} went down: killed 158 | ....restarting {<0.64.0>,#Ref<0.0.3.150>} => {<0.67.0>,#Ref<0.0.3.193>}) 159 | Worker1 (<0.65.0>) is still alive. 160 | Worker3 (<0.66.0>) is still alive. 161 | Worker2 (<0.61.0>) is still alive. 162 | Worker1 (<0.65.0>) is still alive. 163 | Worker1 (<0.65.0>) is still alive. 164 | Worker2 (<0.61.0>) is still alive. 165 | Worker1 (<0.65.0>) is still alive. 166 | Worker3 (<0.66.0>) is still alive. 167 | Worker4 (<0.67.0>) is still alive. 168 | Worker1 (<0.65.0>) is still alive. 169 | kill_rand_worker(): about to kill <0.67.0> 170 | monitor_loop(): Worker {<0.67.0>,#Ref<0.0.3.193>} went down: killed 171 | ....restarting {<0.67.0>,#Ref<0.0.3.193>} => {<0.68.0>,#Ref<0.0.3.207>}) 172 | Worker2 (<0.61.0>) is still alive. 173 | Worker1 (<0.65.0>) is still alive. 174 | Worker1 (<0.65.0>) is still alive. 175 | Worker3 (<0.66.0>) is still alive. 176 | Worker2 (<0.61.0>) is still alive. 177 | Worker1 (<0.65.0>) is still alive. 178 | Worker1 (<0.65.0>) is still alive. 179 | Worker4 (<0.68.0>) is still alive. 180 | Worker2 (<0.61.0>) is still alive. 181 | Worker1 (<0.65.0>) is still alive. 182 | Worker3 (<0.66.0>) is still alive. 183 | Monitor sent stop messages to all workers. 184 | Worker4 stopping: normal. 185 | Worker3 stopping: normal. 186 | Worker1 stopping: normal. 187 | Worker2 stopping: normal. 188 | Monitor terminating: normal. 189 | ``` 190 | There are a couple of things in the program output that demonstrate that eveything is working correctly: 191 | 192 | 1. The worker numbers 1-4 appear in every secton of the output. 193 | 194 | 2. In the restart output, you can actually examine the pid of the process being killed and look for the corresponding Worker number in the prior section of the output, then make a note of that Worker number. Then examine the pid of the new process in the restart output and in the subsequent output check if the new pid corresponds to the same Worker number. 195 | 196 | Checking to make sure that none of the processes that were started are still alive: 197 | ``` 198 | i(). 199 | Pid Initial Call Heap Reds Msgs 200 | Registered Current Function Stack 201 | <0.0.0> otp_ring0:start/2 376 637 0 202 | init init:loop/1 2 203 | <0.1.0> erts_code_purger:start/0 233 4 0 204 | erts_code_purger erts_code_purger:loop/0 3 205 | <0.4.0> erlang:apply/2 2586 119174 0 206 | erl_prim_loader erl_prim_loader:loop/3 5 207 | <0.30.0> gen_event:init_it/6 610 226 0 208 | error_logger gen_event:fetch_msg/5 8 209 | <0.31.0> erlang:apply/2 1598 416 0 210 | application_controlle gen_server:loop/6 7 211 | <0.33.0> application_master:init/4 233 64 0 212 | application_master:main_loop/2 6 213 | <0.34.0> application_master:start_it/4 233 59 0 214 | application_master:loop_it/4 5 215 | <0.35.0> supervisor:kernel/1 610 1765 0 216 | kernel_sup gen_server:loop/6 9 217 | <0.36.0> erlang:apply/2 4185 107483 0 218 | code_server code_server:loop/1 3 219 | <0.38.0> rpc:init/1 233 21 0 220 | rex gen_server:loop/6 9 221 | <0.39.0> global:init/1 233 44 0 222 | global_name_server gen_server:loop/6 9 223 | <0.40.0> erlang:apply/2 233 21 0 224 | global:loop_the_locker/1 5 225 | <0.41.0> erlang:apply/2 233 3 0 226 | global:loop_the_registrar/0 2 227 | <0.42.0> inet_db:init/1 233 249 0 228 | inet_db gen_server:loop/6 9 229 | <0.43.0> global_group:init/1 233 55 0 230 | global_group gen_server:loop/6 9 231 | <0.44.0> file_server:init/1 233 78 0 232 | file_server_2 gen_server:loop/6 9 233 | <0.45.0> supervisor_bridge:standard_error/ 233 34 0 234 | standard_error_sup gen_server:loop/6 9 235 | <0.46.0> erlang:apply/2 233 10 0 236 | standard_error standard_error:server_loop/1 2 237 | <0.47.0> supervisor_bridge:user_sup/1 233 53 0 238 | gen_server:loop/6 9 239 | <0.48.0> user_drv:server/2 1598 4452 0 240 | user_drv user_drv:server_loop/6 9 241 | <0.49.0> group:server/3 610 12679 0 242 | user group:server_loop/3 4 243 | <0.50.0> group:server/3 987 12510 0 244 | group:server_loop/3 4 245 | <0.51.0> erlang:apply/2 4185 9788 0 246 | shell:shell_rep/4 17 247 | <0.52.0> kernel_config:init/1 233 258 0 248 | gen_server:loop/6 9 249 | <0.53.0> supervisor:kernel/1 233 56 0 250 | kernel_safe_sup gen_server:loop/6 9 251 | <0.57.0> erlang:apply/2 2586 18839 0 252 | c:pinfo/1 50 253 | Total 23426 288978 0 254 | 222 255 | ok 256 | 257 | 2> 258 | ``` 259 | No processes from e5 in there! 260 | 261 | Below is a version that uses maps in Erlang 19.2. It also employs `lists:foldl()` which is very similar to `lists:map()` in that they both apply a function to each element in a list. With `lists:foldl()`, you manipulate the second argument in the body of the function, and you pass the altered second argument to the next function call. After the function has been applied to the last element in the list, the manipulated second argument becomes the return value of `lists:foldl()`. In my case, the manipulated second argument is a map, and every time the function is applied to an element of a list, a new entry is entered into the map. After the function is applied to the last element in the list, the map is returned by `lists:foldl()`. 262 | 263 | The difference between `foldl()` and `foldr()` is that `foldr()` applies a function to a list starting on the _right_ end of the list, i.e. the last element of the list, and steps through the list until it reaches the first element in the list. Sometimes the ordering of computations or results is important, so you can choose the version of fold that provides the ordering you desire. 264 | 265 | ```erlang 266 | -module(e5). 267 | %%-compile(export_all). 268 | -export([monitor_workers_init/1, monitor_workers/1]). 269 | -export([stop_monitor/1, test/0]). 270 | 271 | monitor_workers_init(Funcs) -> 272 | spawn(?MODULE, monitor_workers, [Funcs]). 273 | 274 | monitor_workers(Funcs) -> 275 | Workers = lists:foldl( 276 | fun(Func, Map) -> 277 | Map#{spawn_monitor(Func) => Func} %%{Pid, Ref} => Func 278 | end, 279 | #{}, Funcs), %% An empty map is the initial value used for Map in the fun. 280 | io:format("moniter_workers(): Workers: ~n~p~n", [Workers]), 281 | monitor_workers_loop(Workers). 282 | 283 | monitor_workers_loop(Workers) -> 284 | receive 285 | {'DOWN', Ref, process, Pid, Why} -> 286 | io:format("monitor_workers_loop(): Worker ~w went down: ~w~n.", [{Pid, Ref}, Why]), 287 | NewWorkers = restart_worker({Pid, Ref}, Workers), 288 | monitor_workers_loop(NewWorkers); 289 | {request, stop} -> 290 | lists:foreach(fun({Pid,_}) -> Pid ! stop end, 291 | maps:keys(Workers) ), %% {Pid, Ref} => Func 292 | io:format("monitor_workers_loop():~n"), 293 | io:format("\tMonitor finished shutting down workers.~n"), 294 | io:format("\tMonitor terminating normally.~n"); 295 | {request, current_workers, From} -> 296 | From ! {reply, Workers, self()}, 297 | monitor_workers_loop(Workers) 298 | end. 299 | 300 | restart_worker(PidRef, Workers) -> 301 | #{PidRef := Func} = Workers, %%PidRef and Workers are bound! 302 | NewPidRef = spawn_monitor(Func), 303 | io:format("...restarting ~w => ~w) ~n", [PidRef, NewPidRef]), 304 | NewWorkers = maps:remove(PidRef, Workers), 305 | NewWorkers#{NewPidRef => Func}. 306 | 307 | stop_monitor(Monitor) -> 308 | Monitor ! {request, stop}. 309 | 310 | %======== TESTS ========== 311 | 312 | worker(N) -> 313 | receive 314 | stop -> ok 315 | after N*1000 -> 316 | io:format("Worker~w (~w) is still alive.~n", [N, self()] ), 317 | worker(N) 318 | end. 319 | 320 | test() -> 321 | timer:sleep(500), %%Allow output from startup of the erlang shell to print. 322 | 323 | Funcs = [fun() -> worker(N) end || N <- lists:seq(1, 4) ], 324 | Monitor = monitor_workers_init(Funcs), 325 | io:format("Monitor is: ~w~n", [Monitor]), 326 | 327 | timer:sleep(5200), %%Let monitored processes run for awhile. 328 | 329 | FiveTimes = 5, 330 | TimeBetweenKillings = 5200, 331 | kill_rand_worker(FiveTimes, TimeBetweenKillings, Monitor), 332 | 333 | stop_monitor(Monitor). 334 | 335 | kill_rand_worker(0, _, _) -> 336 | ok; 337 | kill_rand_worker(NumTimes, TimeBetweenKillings, Monitor) -> 338 | Workers = get_workers(Monitor), 339 | kill_rand_worker(Workers), 340 | timer:sleep(TimeBetweenKillings), 341 | kill_rand_worker(NumTimes-1, TimeBetweenKillings, Monitor). 342 | 343 | get_workers(Monitor) -> 344 | Monitor ! {request, current_workers, self()}, 345 | receive 346 | {reply, CurrentWorkers, Monitor} -> 347 | CurrentWorkers 348 | end. 349 | 350 | kill_rand_worker(Workers) -> 351 | {Pid, _} = get_random_key(Workers), 352 | io:format("kill_rand_worker(): about to kill ~w~n", [Pid]), 353 | exit(Pid, kill). 354 | 355 | get_random_key(Map) -> 356 | Keys = maps:keys(Map), 357 | RandNum = rand:uniform(length(Keys) ), 358 | lists:nth(RandNum, Keys). 359 | 360 | ``` 361 | -------------------------------------------------------------------------------- /Chapter 13/exercise 6.md: -------------------------------------------------------------------------------- 1 | 1. For my first version, I used monitors. I pretty much followed the literal description in the exercise. 2 | 3 | 2. For my second version, I used links. I created a _link set_ where all the Workers are linked to a MasterLink. That way if one Worker fails, they all fail--including the MasterLink. Then, I created a Monitor process to monitor the MasterLink, which means that when any Worker fails the Monitor detects the MasterLink failing. The only thing that the Monitor has to do after receiving a 'DOWN' message is restart all the Workers. For testing, I used erlang's observer app, `observer:start()`, to kill random Workers. In the observer window, you can click on the Processes tab, then you can right click on a running process, then choose Kill Process. After killing a process, you can select `View > Refresh` in the menu bar to display the new Worker processes that are created. 4 | 5 | Both versions are included below: 6 | 7 | ```erlang 8 | -module(e6). 9 | -compile(export_all). 10 | 11 | monitor_workers_init(Funcs) -> 12 | spawn(?MODULE, monitor_workers, [Funcs]). 13 | 14 | monitor_workers(Funcs) -> 15 | Workers = [ {spawn_monitor(Func), Func} || Func <- Funcs], %% { {Pid,Ref}, Func} 16 | monitor_workers_loop(Workers). 17 | 18 | monitor_workers_loop(Workers) -> 19 | receive 20 | {'DOWN', Ref, process, Pid, Why} -> 21 | io:format("monitor workers_loop(): Worker ~w went down: ~w~n", [{Pid,Ref}, Why]), 22 | io:format("...restarting all workers.~n"), 23 | 24 | stop_workers(Workers), 25 | NewWorkers = [ {spawn_monitor(Func), Func} || {_, Func} <- Workers], 26 | 27 | io:format("monitor workers_loop(): old Workers:~n~p~n", [Workers]), 28 | io:format("monitor workers_loop(): NewWorkers:~n~p~n", [NewWorkers]), 29 | 30 | monitor_workers_loop(NewWorkers); 31 | {request, stop, _From} -> 32 | stop_workers(Workers), 33 | 34 | io:format("monitor workers_loop():~n"), 35 | io:format("\tMonitor finished shutting down workers.~n"), 36 | io:format("\tMonitor terminating normally.~n"); 37 | {request, current_workers, From} -> 38 | From ! {reply, Workers, self()}, 39 | monitor_workers_loop(Workers) 40 | end. 41 | 42 | stop(Monitor) -> 43 | Monitor ! {request, stop, self()}. 44 | 45 | stop_workers(Workers) -> 46 | lists:foreach(fun({{Pid,Ref},_}) -> 47 | demonitor(Ref), 48 | Pid ! stop 49 | end, 50 | Workers). %% { {Pid,Ref}, Func} 51 | 52 | %%==== TESTS ======== 53 | 54 | worker(Id) -> 55 | receive 56 | stop -> ok 57 | after Id*1000 -> 58 | io:format("Worker~w: I'm still alive in ~w~n", [Id, self()]), 59 | worker(Id) 60 | end. 61 | 62 | test() -> 63 | Funcs = [fun() -> worker(Id) end || Id <- lists:seq(1, 4)], 64 | Monitor= monitor_workers_init(Funcs), 65 | 66 | timer:sleep(5200), %%Let worker's run for awhile. 67 | 68 | FiveTimes = 5, 69 | TimeBetweenKillings = 5200, 70 | kill_rand_worker(FiveTimes, TimeBetweenKillings, Monitor), 71 | 72 | stop(Monitor). 73 | 74 | kill_rand_worker(0, _, _) -> 75 | ok; 76 | kill_rand_worker(NumTimes, TimeBetweenKillings, Monitor) -> 77 | Workers = get_workers(Monitor), 78 | kill_rand_worker(Workers), 79 | timer:sleep(TimeBetweenKillings), 80 | kill_rand_worker(NumTimes-1, TimeBetweenKillings, Monitor). 81 | 82 | get_workers(Monitor) -> 83 | Monitor ! {request, current_workers, self()}, 84 | receive 85 | {reply, CurrentWorkers, Monitor} -> 86 | CurrentWorkers 87 | end. 88 | 89 | kill_rand_worker(Workers) -> 90 | RandNum = rand:uniform(length(Workers) ), 91 | {{Pid, _}, _} = lists:nth(RandNum, Workers), %% { {Pid, Ref} Func} 92 | io:format("kill_rand_worker(): about to kill ~w~n", [Pid]), 93 | exit(Pid, kill). 94 | ``` 95 | 96 | In the shell: 97 | 98 | ``` 99 | $ ./run 100 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 101 | Eshell V8.2 (abort with ^G) 102 | 103 | 1> Worker1: I'm still alive in <0.58.0> 104 | Worker2: I'm still alive in <0.59.0> 105 | Worker1: I'm still alive in <0.58.0> 106 | Worker3: I'm still alive in <0.60.0> 107 | Worker1: I'm still alive in <0.58.0> 108 | Worker4: I'm still alive in <0.61.0> 109 | Worker2: I'm still alive in <0.59.0> 110 | Worker1: I'm still alive in <0.58.0> 111 | Worker1: I'm still alive in <0.58.0> 112 | kill_rand_worker(): about to kill <0.59.0> 113 | monitor workers_loop(): Worker {<0.59.0>,#Ref<0.0.4.127>} went down: killed 114 | ...restarting all workers. 115 | monitor workers_loop(): old Workers: 116 | [{{<0.58.0>,#Ref<0.0.4.126>},#Fun}, 117 | {{<0.59.0>,#Ref<0.0.4.127>},#Fun}, 118 | {{<0.60.0>,#Ref<0.0.4.128>},#Fun}, 119 | {{<0.61.0>,#Ref<0.0.4.129>},#Fun}] 120 | monitor workers_loop(): NewWorkers: 121 | [{{<0.64.0>,#Ref<0.0.4.148>},#Fun}, 122 | {{<0.65.0>,#Ref<0.0.4.149>},#Fun}, 123 | {{<0.66.0>,#Ref<0.0.4.150>},#Fun}, 124 | {{<0.67.0>,#Ref<0.0.4.151>},#Fun}] 125 | Worker1: I'm still alive in <0.64.0> 126 | Worker2: I'm still alive in <0.65.0> 127 | Worker1: I'm still alive in <0.64.0> 128 | Worker3: I'm still alive in <0.66.0> 129 | Worker1: I'm still alive in <0.64.0> 130 | Worker4: I'm still alive in <0.67.0> 131 | Worker2: I'm still alive in <0.65.0> 132 | Worker1: I'm still alive in <0.64.0> 133 | Worker1: I'm still alive in <0.64.0> 134 | kill_rand_worker(): about to kill <0.66.0> 135 | monitor workers_loop(): Worker {<0.66.0>,#Ref<0.0.4.150>} went down: killed 136 | ...restarting all workers. 137 | monitor workers_loop(): old Workers: 138 | [{{<0.64.0>,#Ref<0.0.4.148>},#Fun}, 139 | {{<0.65.0>,#Ref<0.0.4.149>},#Fun}, 140 | {{<0.66.0>,#Ref<0.0.4.150>},#Fun}, 141 | {{<0.67.0>,#Ref<0.0.4.151>},#Fun}] 142 | monitor workers_loop(): NewWorkers: 143 | [{{<0.68.0>,#Ref<0.0.4.167>},#Fun}, 144 | {{<0.69.0>,#Ref<0.0.4.168>},#Fun}, 145 | {{<0.70.0>,#Ref<0.0.4.169>},#Fun}, 146 | {{<0.71.0>,#Ref<0.0.4.170>},#Fun}] 147 | Worker1: I'm still alive in <0.68.0> 148 | Worker2: I'm still alive in <0.69.0> 149 | Worker1: I'm still alive in <0.68.0> 150 | Worker3: I'm still alive in <0.70.0> 151 | Worker1: I'm still alive in <0.68.0> 152 | Worker4: I'm still alive in <0.71.0> 153 | Worker2: I'm still alive in <0.69.0> 154 | Worker1: I'm still alive in <0.68.0> 155 | Worker1: I'm still alive in <0.68.0> 156 | kill_rand_worker(): about to kill <0.70.0> 157 | monitor workers_loop(): Worker {<0.70.0>,#Ref<0.0.4.169>} went down: killed 158 | ...restarting all workers. 159 | monitor workers_loop(): old Workers: 160 | [{{<0.68.0>,#Ref<0.0.4.167>},#Fun}, 161 | {{<0.69.0>,#Ref<0.0.4.168>},#Fun}, 162 | {{<0.70.0>,#Ref<0.0.4.169>},#Fun}, 163 | {{<0.71.0>,#Ref<0.0.4.170>},#Fun}] 164 | monitor workers_loop(): NewWorkers: 165 | [{{<0.72.0>,#Ref<0.0.4.185>},#Fun}, 166 | {{<0.73.0>,#Ref<0.0.4.186>},#Fun}, 167 | {{<0.74.0>,#Ref<0.0.4.187>},#Fun}, 168 | {{<0.75.0>,#Ref<0.0.4.188>},#Fun}] 169 | Worker1: I'm still alive in <0.72.0> 170 | Worker2: I'm still alive in <0.73.0> 171 | Worker1: I'm still alive in <0.72.0> 172 | Worker3: I'm still alive in <0.74.0> 173 | Worker1: I'm still alive in <0.72.0> 174 | Worker4: I'm still alive in <0.75.0> 175 | Worker2: I'm still alive in <0.73.0> 176 | Worker1: I'm still alive in <0.72.0> 177 | Worker1: I'm still alive in <0.72.0> 178 | kill_rand_worker(): about to kill <0.73.0> 179 | monitor workers_loop(): Worker {<0.73.0>,#Ref<0.0.4.186>} went down: killed 180 | ...restarting all workers. 181 | monitor workers_loop(): old Workers: 182 | [{{<0.72.0>,#Ref<0.0.4.185>},#Fun}, 183 | {{<0.73.0>,#Ref<0.0.4.186>},#Fun}, 184 | {{<0.74.0>,#Ref<0.0.4.187>},#Fun}, 185 | {{<0.75.0>,#Ref<0.0.4.188>},#Fun}] 186 | monitor workers_loop(): NewWorkers: 187 | [{{<0.76.0>,#Ref<0.0.4.203>},#Fun}, 188 | {{<0.77.0>,#Ref<0.0.4.204>},#Fun}, 189 | {{<0.78.0>,#Ref<0.0.4.205>},#Fun}, 190 | {{<0.79.0>,#Ref<0.0.4.206>},#Fun}] 191 | Worker1: I'm still alive in <0.76.0> 192 | Worker2: I'm still alive in <0.77.0> 193 | Worker1: I'm still alive in <0.76.0> 194 | Worker3: I'm still alive in <0.78.0> 195 | Worker1: I'm still alive in <0.76.0> 196 | Worker4: I'm still alive in <0.79.0> 197 | Worker2: I'm still alive in <0.77.0> 198 | Worker1: I'm still alive in <0.76.0> 199 | Worker1: I'm still alive in <0.76.0> 200 | kill_rand_worker(): about to kill <0.78.0> 201 | monitor workers_loop(): Worker {<0.78.0>,#Ref<0.0.4.205>} went down: killed 202 | ...restarting all workers. 203 | monitor workers_loop(): old Workers: 204 | [{{<0.76.0>,#Ref<0.0.4.203>},#Fun}, 205 | {{<0.77.0>,#Ref<0.0.4.204>},#Fun}, 206 | {{<0.78.0>,#Ref<0.0.4.205>},#Fun}, 207 | {{<0.79.0>,#Ref<0.0.4.206>},#Fun}] 208 | monitor workers_loop(): NewWorkers: 209 | [{{<0.80.0>,#Ref<0.0.4.221>},#Fun}, 210 | {{<0.81.0>,#Ref<0.0.4.222>},#Fun}, 211 | {{<0.82.0>,#Ref<0.0.4.223>},#Fun}, 212 | {{<0.83.0>,#Ref<0.0.4.224>},#Fun}] 213 | Worker1: I'm still alive in <0.80.0> 214 | Worker2: I'm still alive in <0.81.0> 215 | Worker1: I'm still alive in <0.80.0> 216 | Worker3: I'm still alive in <0.82.0> 217 | Worker1: I'm still alive in <0.80.0> 218 | Worker4: I'm still alive in <0.83.0> 219 | Worker2: I'm still alive in <0.81.0> 220 | Worker1: I'm still alive in <0.80.0> 221 | Worker1: I'm still alive in <0.80.0> 222 | monitor workers_loop(): 223 | Monitor finished shutting down workers. 224 | Monitor terminating normally. 225 | ``` 226 | Check to make sure there are no leftover processes: 227 | ``` 228 | i(). 229 | Pid Initial Call Heap Reds Msgs 230 | Registered Current Function Stack 231 | <0.0.0> otp_ring0:start/2 376 637 0 232 | init init:loop/1 2 233 | <0.1.0> erts_code_purger:start/0 233 4 0 234 | erts_code_purger erts_code_purger:loop/0 3 235 | <0.4.0> erlang:apply/2 987 119748 0 236 | erl_prim_loader erl_prim_loader:loop/3 5 237 | <0.30.0> gen_event:init_it/6 610 226 0 238 | error_logger gen_event:fetch_msg/5 8 239 | <0.31.0> erlang:apply/2 1598 416 0 240 | application_controlle gen_server:loop/6 7 241 | <0.33.0> application_master:init/4 233 64 0 242 | application_master:main_loop/2 6 243 | <0.34.0> application_master:start_it/4 233 59 0 244 | application_master:loop_it/4 5 245 | <0.35.0> supervisor:kernel/1 610 1784 0 246 | kernel_sup gen_server:loop/6 9 247 | <0.36.0> erlang:apply/2 4185 107485 0 248 | code_server code_server:loop/1 3 249 | <0.38.0> rpc:init/1 233 21 0 250 | rex gen_server:loop/6 9 251 | <0.39.0> global:init/1 233 44 0 252 | global_name_server gen_server:loop/6 9 253 | <0.40.0> erlang:apply/2 233 21 0 254 | global:loop_the_locker/1 5 255 | <0.41.0> erlang:apply/2 233 3 0 256 | global:loop_the_registrar/0 2 257 | <0.42.0> inet_db:init/1 233 255 0 258 | inet_db gen_server:loop/6 9 259 | <0.43.0> global_group:init/1 233 55 0 260 | global_group gen_server:loop/6 9 261 | <0.44.0> file_server:init/1 233 78 0 262 | file_server_2 gen_server:loop/6 9 263 | <0.45.0> supervisor_bridge:standard_error/ 233 34 0 264 | standard_error_sup gen_server:loop/6 9 265 | <0.46.0> erlang:apply/2 233 10 0 266 | standard_error standard_error:server_loop/1 2 267 | <0.47.0> supervisor_bridge:user_sup/1 233 53 0 268 | gen_server:loop/6 9 269 | <0.48.0> user_drv:server/2 1598 4545 0 270 | user_drv user_drv:server_loop/6 9 271 | <0.49.0> group:server/3 1598 16834 0 272 | user group:server_loop/3 4 273 | <0.50.0> group:server/3 987 12510 0 274 | group:server_loop/3 4 275 | <0.51.0> erlang:apply/2 4185 9788 0 276 | shell:shell_rep/4 17 277 | <0.52.0> kernel_config:init/1 233 258 0 278 | gen_server:loop/6 9 279 | <0.53.0> supervisor:kernel/1 233 56 0 280 | kernel_safe_sup gen_server:loop/6 9 281 | <0.62.0> erlang:apply/2 2586 18839 0 282 | c:pinfo/1 50 283 | Total 22815 293827 0 284 | 222 285 | ok 286 | 2> 287 | ``` 288 | 289 | Here's a version that uses links: 290 | ```erlang 291 | -module(e6). 292 | %%-compile(export_all). 293 | -export([monitor_workers_init/1, monitor_workers/1]). 294 | -export([test/0, worker/1]). 295 | 296 | monitor_workers_init(NumWorkers) -> 297 | register(?MODULE, Pid = spawn(?MODULE, monitor_workers, [NumWorkers]) ), 298 | Pid. 299 | 300 | monitor_workers(NumWorkers) -> 301 | MasterLink = create_link_set(NumWorkers), %% => {Pid, Ref} 302 | monitor_workers_loop(MasterLink, NumWorkers). 303 | 304 | monitor_workers_loop({MasterPid, MasterRef}, NumWorkers) -> 305 | receive 306 | {'DOWN', MasterRef, process, MasterPid, Why} -> 307 | io:format("monitor_workers_loop(): MasterLink ~w went down: ~w~n", [{MasterPid,MasterRef}, Why]), 308 | io:format("...restarting all workers.~n"), 309 | 310 | NewMasterLink = create_link_set(NumWorkers), 311 | monitor_workers_loop(NewMasterLink, NumWorkers); 312 | {request, stop, _From} -> 313 | exit(MasterPid, kill), 314 | 315 | io:format("\tMonitor sent kill signal to MasterLink.~n"), 316 | io:format("\tMonitor terminating normally.~n") 317 | end. 318 | 319 | create_link_set(NumWorkers) -> 320 | NewMasterLink = 321 | fun() -> 322 | NewWorkers = [ spawn_link(?MODULE, worker, [Id]) || Id <- lists:seq(1, NumWorkers)], 323 | io:format("create_link_set(): NewWorkers: ~w~n", [NewWorkers]), 324 | receive 325 | after infinity -> ok %%MasterLink sits and does nothing. 326 | end 327 | end, 328 | MasterPidRef = spawn_monitor(NewMasterLink), %% monitor_workers_loop() monitors MasterLink 329 | io:format("create_link_set(): NewMasterLink: ~w~n", [MasterPidRef]), 330 | MasterPidRef. 331 | 332 | stop() -> 333 | ?MODULE ! {request, stop, self()}. 334 | 335 | 336 | %%==== TESTS ======== 337 | 338 | worker(Id) -> 339 | receive 340 | stop -> ok 341 | after Id*1000 -> 342 | io:format("Worker~w: I'm still alive in ~w~n", [Id, self()]), 343 | worker(Id) 344 | end. 345 | 346 | test() -> 347 | observer:start(), %%I use the Processes tab to kill random workers. 348 | timer:sleep(500), %%Allow output from erlang shell startup to print. 349 | 350 | NumWorkers = 4, 351 | monitor_workers_init(NumWorkers), 352 | 353 | timer:sleep(30000), %%Give me time to kill some processes in observer app. 354 | 355 | stop(). 356 | ``` 357 | 358 | In the shell: 359 | ``` 360 | $ ./run 361 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 362 | Eshell V8.2 (abort with ^G) 363 | 364 | 1> create_link_set(): NewMasterLink: {<0.79.0>,#Ref<0.0.4.279>} 365 | create_link_set(): NewWorkers: [<0.80.0>,<0.81.0>,<0.82.0>,<0.83.0>] 366 | Worker1: I'm still alive in <0.80.0> 367 | Worker2: I'm still alive in <0.81.0> 368 | Worker1: I'm still alive in <0.80.0> 369 | Worker3: I'm still alive in <0.82.0> 370 | Worker1: I'm still alive in <0.80.0> 371 | Worker4: I'm still alive in <0.83.0> 372 | Worker2: I'm still alive in <0.81.0> 373 | Worker1: I'm still alive in <0.80.0> 374 | Worker1: I'm still alive in <0.80.0> 375 | Worker3: I'm still alive in <0.82.0> 376 | Worker2: I'm still alive in <0.81.0> 377 | Worker1: I'm still alive in <0.80.0> 378 | Worker1: I'm still alive in <0.80.0> 379 | monitor_workers_loop(): MasterLink {<0.79.0>,#Ref<0.0.4.279>} went down: killed 380 | ...restarting all workers. 381 | create_link_set(): NewMasterLink: {<0.575.0>,#Ref<0.0.4.1781>} 382 | create_link_set(): NewWorkers: [<0.576.0>,<0.577.0>,<0.578.0>,<0.579.0>] 383 | Worker1: I'm still alive in <0.576.0> 384 | Worker2: I'm still alive in <0.577.0> 385 | Worker1: I'm still alive in <0.576.0> 386 | Worker3: I'm still alive in <0.578.0> 387 | Worker1: I'm still alive in <0.576.0> 388 | Worker4: I'm still alive in <0.579.0> 389 | Worker2: I'm still alive in <0.577.0> 390 | Worker1: I'm still alive in <0.576.0> 391 | Worker1: I'm still alive in <0.576.0> 392 | Worker3: I'm still alive in <0.578.0> 393 | Worker2: I'm still alive in <0.577.0> 394 | Worker1: I'm still alive in <0.576.0> 395 | Worker1: I'm still alive in <0.576.0> 396 | monitor_workers_loop(): MasterLink {<0.575.0>,#Ref<0.0.4.1781>} went down: killed 397 | ...restarting all workers. 398 | create_link_set(): NewMasterLink: {<0.1023.0>,#Ref<0.0.4.3138>} 399 | create_link_set(): NewWorkers: [<0.1024.0>,<0.1025.0>,<0.1026.0>,<0.1027.0>] 400 | Worker1: I'm still alive in <0.1024.0> 401 | Worker2: I'm still alive in <0.1025.0> 402 | Worker1: I'm still alive in <0.1024.0> 403 | Worker3: I'm still alive in <0.1026.0> 404 | Worker1: I'm still alive in <0.1024.0> 405 | Worker4: I'm still alive in <0.1027.0> 406 | Worker2: I'm still alive in <0.1025.0> 407 | Worker1: I'm still alive in <0.1024.0> 408 | Worker1: I'm still alive in <0.1024.0> 409 | Worker3: I'm still alive in <0.1026.0> 410 | Worker2: I'm still alive in <0.1025.0> 411 | Worker1: I'm still alive in <0.1024.0> 412 | Worker1: I'm still alive in <0.1024.0> 413 | monitor_workers_loop(): MasterLink {<0.1023.0>,#Ref<0.0.4.3138>} went down: killed 414 | ...restarting all workers. 415 | create_link_set(): NewMasterLink: {<0.1303.0>,#Ref<0.0.4.3992>} 416 | create_link_set(): NewWorkers: [<0.1304.0>,<0.1305.0>,<0.1306.0>,<0.1307.0>] 417 | Worker1: I'm still alive in <0.1304.0> 418 | Worker2: I'm still alive in <0.1305.0> 419 | Worker1: I'm still alive in <0.1304.0> 420 | Worker3: I'm still alive in <0.1306.0> 421 | Worker1: I'm still alive in <0.1304.0> 422 | Worker4: I'm still alive in <0.1307.0> 423 | Worker2: I'm still alive in <0.1305.0> 424 | Worker1: I'm still alive in <0.1304.0> 425 | Worker1: I'm still alive in <0.1304.0> 426 | Worker3: I'm still alive in <0.1306.0> 427 | Worker2: I'm still alive in <0.1305.0> 428 | Worker1: I'm still alive in <0.1304.0> 429 | monitor_workers_loop(): MasterLink {<0.1303.0>,#Ref<0.0.4.3992>} went down: killed 430 | ...restarting all workers. 431 | create_link_set(): NewMasterLink: {<0.1792.0>,#Ref<0.0.4.5469>} 432 | create_link_set(): NewWorkers: [<0.1793.0>,<0.1794.0>,<0.1795.0>,<0.1796.0>] 433 | Worker1: I'm still alive in <0.1793.0> 434 | Monitor sent kill signal to MasterLink. 435 | Monitor terminating normally. 436 | ``` 437 | 438 | After closing the observer window, I checked to see if there were any left over processes from my program: 439 | 440 | ``` 441 | i(). 442 | Pid Initial Call Heap Reds Msgs 443 | Registered Current Function Stack 444 | <0.0.0> otp_ring0:start/2 376 637 0 445 | init init:loop/1 2 446 | <0.1.0> erts_code_purger:start/0 233 4 0 447 | erts_code_purger erts_code_purger:loop/0 3 448 | <0.4.0> erlang:apply/2 1598 250248 0 449 | erl_prim_loader erl_prim_loader:loop/3 5 450 | <0.30.0> gen_event:init_it/6 610 242 0 451 | error_logger gen_event:fetch_msg/5 8 452 | <0.31.0> erlang:apply/2 1598 416 0 453 | application_controlle gen_server:loop/6 7 454 | <0.33.0> application_master:init/4 233 64 0 455 | application_master:main_loop/2 6 456 | <0.34.0> application_master:start_it/4 233 59 0 457 | application_master:loop_it/4 5 458 | <0.35.0> supervisor:kernel/1 610 1765 0 459 | kernel_sup gen_server:loop/6 9 460 | <0.36.0> erlang:apply/2 6772 117111 0 461 | code_server code_server:loop/1 3 462 | <0.38.0> rpc:init/1 233 21 0 463 | rex gen_server:loop/6 9 464 | <0.39.0> global:init/1 233 44 0 465 | global_name_server gen_server:loop/6 9 466 | <0.40.0> erlang:apply/2 233 21 0 467 | global:loop_the_locker/1 5 468 | <0.41.0> erlang:apply/2 233 3 0 469 | global:loop_the_registrar/0 2 470 | <0.42.0> inet_db:init/1 233 249 0 471 | inet_db gen_server:loop/6 9 472 | <0.43.0> global_group:init/1 233 55 0 473 | global_group gen_server:loop/6 9 474 | <0.44.0> file_server:init/1 610 447 0 475 | file_server_2 gen_server:loop/6 9 476 | <0.45.0> supervisor_bridge:standard_error/ 233 34 0 477 | standard_error_sup gen_server:loop/6 9 478 | <0.46.0> erlang:apply/2 233 10 0 479 | standard_error standard_error:server_loop/1 2 480 | <0.47.0> supervisor_bridge:user_sup/1 233 53 0 481 | gen_server:loop/6 9 482 | <0.48.0> user_drv:server/2 2586 4172 0 483 | user_drv user_drv:server_loop/6 9 484 | <0.49.0> group:server/3 2586 11381 0 485 | user group:server_loop/3 4 486 | <0.50.0> group:server/3 987 12514 0 487 | group:server_loop/3 4 488 | <0.51.0> erlang:apply/2 4185 9788 0 489 | shell:shell_rep/4 17 490 | <0.52.0> kernel_config:init/1 233 258 0 491 | gen_server:loop/6 9 492 | <0.53.0> supervisor:kernel/1 233 131 0 493 | kernel_safe_sup gen_server:loop/6 9 494 | <0.58.0> erlang:apply/2 2586 18804 0 495 | c:pinfo/1 50 496 | <0.61.0> wxe_master:init/1 2586 747 0 497 | wxe_master gen_server:loop/6 9 498 | <0.62.0> erlang:apply/2 610 12865 0 499 | timer:sleep/1 5 500 | <0.64.0> timer:init/1 376 491 0 501 | timer_server gen_server:loop/6 9 502 | Total 31938 442634 0 503 | 245 504 | ok 505 | 2> 506 | ``` 507 | Nothing that starts with `e6` in there! 508 | 509 | 510 | 511 | 512 | 513 | 514 | -------------------------------------------------------------------------------- /Chapter 14/exercise 1.md: -------------------------------------------------------------------------------- 1 | In Terminal window #1: 2 | ``` 3 | ~$ erl -sname gandalf 4 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 5 | Eshell V8.2 (abort with ^G) 6 | 7 | (gandalf@7studsMBP)1> 8 | 9 | ``` 10 | 11 | In Terminal window #2: 12 | ``` 13 | $ erl -sname bilbo 14 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 15 | Eshell V8.2 (abort with ^G) 16 | 17 | (bilbo@7studsMBP)1> nodes(). 18 | [] 19 | 20 | (bilbo@7studsMBP)2> net_adm:ping('gandalf@7studsMBP'). %Activate the gandalf Node. 21 | pong 22 | 23 | (bilbo@7studsMBP)3> nodes(). 24 | [gandalf@7studsMBP] 25 | 26 | (bilbo@7studsMBP)4> c(ml). 27 | {ok,ml} 28 | 29 | (bilbo@7studsMBP)5> nl(ml). %Load the ml (mylib) module on the gandalf Node. 30 | abcast 31 | 32 | (bilbo@7studsMBP)6> ml:start('gandalf@7studsMBP'). 33 | Enter your name: 7stud 34 | Hello, 7stud! 35 | ok 36 | 37 | (bilbo@7studsMBP)7> ml:start('gandalf@7studsMBP'). 38 | Enter your name: 7stud 39 | Fine morning, 7stud! 40 | ok 41 | 42 | (bilbo@7studsMBP)8> 43 | ``` 44 | 45 | The code: 46 | ```erlang 47 | -module(ml). 48 | -compile(export_all). 49 | 50 | get_greeting() -> 51 | timer:sleep(1000 * rand:uniform(5)), %Mimic doing some work, like accessing websites to get interesting greetings. 52 | Greetings = ["Hello", "Good day", "Fine morning"], 53 | lists:nth( 54 | rand:uniform(length(Greetings)), 55 | Greetings). 56 | 57 | start(Node) -> 58 | Key = rpc:async_call(Node, 59 | ml, get_greeting, [] ), %Execution continues immediately on the next line. 60 | 61 | Input = io:get_line("Enter your name: "), %While the user is entering their name, the other node is doing some work. 62 | Name = string:strip(Input, right, $\n), 63 | io:format("~s, ~s!~n", [rpc:yield(Key), Name]). %If Key process hasn't returned yet, 64 | %yield() blocks and waits for the return value. 65 | 66 | ``` 67 | -------------------------------------------------------------------------------- /Chapter 14/exercise 2.md: -------------------------------------------------------------------------------- 1 | In a Terminal window on computer #1 (or using an ssh session on computer #2 like I did--see instructions below): 2 | ``` 3 | $ ssh FirstLast@new-host.home 4 | Password: 5 | Last login: Mon Jun 5 00:51:31 2017 from 7studsmbp.home 6 | 7 | ~$ erl -name gandalf -setcookie abc 8 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] 9 | Eshell V8.2 (abort with ^G) 10 | 11 | (gandalf@new-host.home)1> 12 | ``` 13 | 14 | In another Terminal window on computer #2: 15 | ``` 16 | $ erl -name bilbo -setcookie abc 17 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 18 | Eshell V8.2 (abort with ^G) 19 | 20 | (bilbo@7studsMBP.home)1> nodes(). 21 | [] 22 | 23 | (bilbo@7studsMBP.home)2> net_adm:ping('gandalf@new-host.home'). 24 | pong 25 | 26 | (bilbo@7studsMBP.home)3> nodes(). 27 | ['gandalf@new-host.home'] 28 | 29 | (bilbo@7studsMBP.home)4> c(ml). 30 | {ok,ml} 31 | 32 | (bilbo@7studsMBP.home)5> nl(ml). 33 | abcast 34 | 35 | (bilbo@7studsMBP.home)6> ml:start('gandalf@new-host.home'). 36 | Enter your name: 7stud 37 | Fine morning, 7stud! 38 | ok 39 | 40 | (bilbo@7studsMBP.home)7> 41 | ``` 42 | 43 | The code: 44 | ```erlang 45 | -module(ml). 46 | -compile(export_all). 47 | 48 | get_greeting() -> 49 | timer:sleep(1000 * rand:uniform(5)), %Mimic doing some work, like accessing websites to get interesting greetings. 50 | Greetings = ["Hello", "Good day", "Fine morning"], 51 | lists:nth( 52 | rand:uniform(length(Greetings)), 53 | Greetings). 54 | 55 | start(Node) -> 56 | Key = rpc:async_call(Node, 57 | ml, get_greeting, [] ), %Execution continues immediately on the next line. 58 | 59 | Input = io:get_line("Enter your name: "), %While the user is entering their name, the other node is doing some work. 60 | Name = string:strip(Input, right, $\n), 61 | io:format("~s, ~s!~n", [rpc:yield(Key), Name]). %If Key process hasn't returned yet, 62 | %yield() blocks and waits for the return value. 63 | 64 | ``` 65 | 66 | ### ssh setup: 67 | 68 | Mac#1 (the Server) iMac running OSX 10.7.5/Erlang 19.2: 69 | ------------------- 70 | 1. System Preferences > Security & Privacy > Firewall: 71 | --Turn Off Firewall 72 | 73 | 2. System Preferences > Sharing: 74 | --Check Remote Login checkbox. 75 | --Allow access for: Only these Users: Select a user whose password you know. 76 | --The green light next to “Remote Login: On” should be lit. 77 | --Underneath the green light it says: To log in to this computer remotely type: 78 | ```“ssh FirstLast@new-host.home”. ``` 79 | You will use whatever it says on that line (without the quotes) for starting an ssh session. 80 | 81 | Mac#2 (the Client) Macbook Pro running OSX 10.10.5/Erlang 19.2: 82 | --------------------- 83 | 1. Open a Terminal window. 84 | 85 | 2. Start an ssh session: 86 | ``` 87 | ~$ ssh FirstLast@new-host.home 88 | Password: 89 | Last login: Mon Dec 1 11:30:22 1900 from 7studsMB.home 90 | ``` 91 | (The first time I opened an ssh session, I was presented with a message that said something like, “blah blah keys couldn’t be verified. Continue (yes/no)?” I typed: yes) 92 | ``` 93 | ~$ cd erlang_programs/ (this is the directory on Mac#1 that contains kvs.erl and kvs.beam. 94 | ~/erlang_programs$ ls 95 | kvs.beam kvs.erl 96 | 97 | ~/erlang_programs$ erl -name gandalf -setcookie abc 98 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] 99 | Eshell V8.2 (abort with ^G) 100 | 101 | (gandalf@new-host.home)1> kvs:start(). 102 | true 103 | (gandalf@new-host.home)2> 104 | ``` 105 | 4. Open another Terminal window. 106 | 107 | 5. It doesn’t matter what directory you are in: 108 | ``` 109 | $ erl -name bilbo -setcookie abc 110 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 111 | Eshell V8.2 (abort with ^G) 112 | 113 | (bilbo@7studsMBP.home)1> 114 | ``` 115 | 6. 116 | ``` 117 | (bilbo@7studsMBP.home)1> rpc:call('gandalf@new-host.home', kvs, store, [weather, cold]). 118 | true 119 | ``` 120 | Note that the fully qualified host name needs to be atom quoted--otherwise you will get the error: ```* 1: syntax error before: '.' ``` Also, note that the fully qualified host name that worked for me is the exact same thing appearing in the prompt for the ssh session in the first Terminal window. 121 | 122 | 7. 123 | ``` 124 | (bilbo@7studsMBP.home)2> rpc:call('gandalf@new-host.home', kvs, lookup, [weather]). 125 | {ok,cold} 126 | (bilbo@7studsMBP.home)3> 127 | ``` 128 | 8. You can end the ssh session like this: 129 | ``` 130 | (bilbo@7studsMBP.home)3> q(). 131 | ~/erlang_programs$ exit 132 | logout 133 | Connection to new-host.home closed. 134 | ~$ 135 | ``` 136 | Mac#1 (the Server): 137 | ------------------- 138 | 1. System Preferences > Sharing: 139 | --Uncheck Remote Login checkbox. 140 | 141 | 2. System Preferences > Security & Privacy > Firewall: 142 | --Turn On Firewall 143 | -------------------------------------------------------------------------------- /Chapter 15/_main_example_explanation.md: -------------------------------------------------------------------------------- 1 | ### Explanation of C code. 2 | 3 | First note that in the erlang code when you specify `{packet, 2}` as a port option, see p. 240, (or as a TCP socket option in chapter 17), erlang automatically calculates the Length of the message that you are sending, then prepends two bytes containing the Length to the message. You have to assume that when you send a message, the message will get split up into an indeterminate number of chunks of indeterminate size. Thus the other side needs some indicator to know when to stop reading when it has received the whole message. 4 | 5 | I'm also going to use some pseudo code mixed in with the C code in my explanations. 6 | 7 | ```read_exact([], 2)``` 8 | 9 | That tries to read two bytes into the empty array, where the two bytes represent the Length of the message sent by the erlang code. 10 | ``` 11 | buf (for buffer, i.e. an array to store bytes) 12 | | 13 | V 14 | i = read(0, [], 2) 15 | ``` 16 | That reads from `stdin`, which is identified with a 0, and inserts the bytes read into the empty array. `2` is the number of bytes to read. `buf+got` is pointer arithmetic and moves the `buf` pointer to a new spot in the array marking the end of the data read so far. `read()` returns: 17 | 18 | 1. The number of bytes read, or 19 | 2. 0 if read() hits end-of-file, or 20 | 3. a negative number if read() encounters an error. 21 | 22 | The trickiest bit is in the function ```read_cmd()```: 23 | 24 | len = (buf[0] << 8 ) | buf[1]); 25 | 26 | When you use the left bitshift operator on buf[0], which is an unsigned char (or one byte long), the bits are _not_ shifted off the left end which would result in 0. Rather, buf[0] is first converted to an int type (probably 4 bytes long), then the bits are shifted left. To illustrate how the bitshifting(<<) and bit OR'ing (|) works an example should prove illustrative. 27 | 28 | Suppose the length of the message sent to the C code is 258. That means the two bytes inserted into buf will look like this: 29 | 30 | buf = [0000 0001, 0000 0010] 31 | 32 | Note that `0000 0001 0000 0010` is 256. When you specify `{packet, 2}`, erlang automatically calculates the Length of the message you are sending, then prepends two bytes containing the Length of the message to the beginning of the message. The other side can then read the first two bytes from stdin to get the Length of the message, then stop reading when it has read Length bytes. 33 | 34 | The question is how do you convert buf[0], which is the binary representation for 1, and buf[1], which is the binary representation for 2, back into 258? 35 | 36 | 0000 00001 << 8 ==> ... 0000 0001 0000 0000 (That is 256.) 37 | 38 | Then if you OR that result with buf[1]: 39 | 40 | len = ... 000 0001 0000 0000 | buf[1]); 41 | 42 | first buf[1] will automatically be converted to an int, then the OR'ing will be done: 43 | 44 | ``` 45 | ... 0000 0001 0000 0000 46 | | 47 | ... 0000 0000 0000 0010 48 | -------------------- 49 | ... 0000 0001 0000 0010 50 | ``` 51 | 52 | As a result, you end up combining two single bytes into an int containg the original length of 258. 53 | 54 | `write_cmd()` does the reverse and converts an integer, representing the length of the message containing the result, into two single bytes: 55 | 56 | first_byte = (len >> 8) & 0xff; 57 | 58 | Suppose once again that the length (len) of the message to be written to stdout is 258 bytes: 59 | 60 | len = .... 0000 0001 0000 0010 61 | 62 | This time bits _are_ shifted off the end: 63 | 64 | ... 0000 0001 0000 0010 >> 8 ==> ... 0000 0000 0000 0001 65 | 66 | 67 | Now, here is what happens when you AND that result with 0xff: 68 | 69 | ``` 70 | ... 0000 0000 0000 0001 71 | & 72 | ... 0000 0000 1111 1111 73 | ------------------------ 74 | ... 0000 0000 0000 0001 75 | 76 | ``` 77 | 78 | AND'ing with 0xff effectively zeros out the bits to the left of the first byte while retaining all the 1's in the first byte. Finally, assigning that result to an unsigned char type, which is one byte long, serves to truncate the leftmost bits, giving you: 79 | 80 | first_byte = 0000 0001; 81 | 82 | To get the second byte for the length of the message, the example code then AND's the original length with 0xff: 83 | 84 | second_byte = len & 0xff 85 | 86 | AND'ing accomplishes this: 87 | 88 | ``` 89 | ... 0000 0001 0000 0010 90 | & ... 0000 0000 1111 1111 91 | ------------------------- 92 | ... 0000 0000 0000 0010 93 | ``` 94 | Then assigning that int to an unsigned char type truncates the int: 95 | 96 | 0000 0010 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Chapter 15/exercise 1.md: -------------------------------------------------------------------------------- 1 | If you waded through the C code and finally got to the makefile, you might wonder WTF? `example_lid.c`? `unit_test`? Where are those files? At the start of the chapter, on (the unmarked) p. 233, the book describes running foreign language code _inside_ the Erlang virtual machine, and calls that method _unsafe_. Then the book goes on to say that the chapter won't be covering that method. Yet, `example_lid.c` is for doing that _unsafe_ thing that supposedly wasn't going to be covered, and therefore it doesn't belong in the example. 2 | 3 | Below is a modern makefile (see my answers for chapter 10 for an explanation of how the makefile works) that I created for compiling just the files discussed in the chapter, which are: 4 | ``` 5 | example1.c 6 | example1_driver.c 7 | erl_comm.c 8 | 9 | example1.erl 10 | ``` 11 | I decided to incorporate a modified version of the very simple unit_test file, which can be found in the source code, and my version is also included below. 12 | 13 | makefile: 14 | ``` 15 | modules := example1 unit_test 16 | 17 | .PHONY: all 18 | all: ${modules:%=%.beam} example1 19 | @erl -noshell -s unit_test start 20 | 21 | %.beam: %.erl 22 | erlc -W $< 23 | 24 | example1: example1.c erl_comm.c example1_driver.c 25 | gcc -o example1 example1.c erl_comm.c example1_driver.c 26 | 27 | .PHONY: clean 28 | clean: 29 | rm example1 *.beam 30 | ``` 31 | 32 | (modified) unit_test: 33 | ```erlang 34 | -module(unit_test). 35 | -export([start/0]). 36 | 37 | start() -> 38 | io:format("Testing drivers~n"), 39 | example1:start(), 40 | 6 = example1:twice(3), 41 | 10 = example1:sum(6,4), 42 | 43 | %example1_lid:start(), 44 | %8 = example1_lid:twice(4), 45 | %20 = example1_lid:sum(15,5), 46 | 47 | io:format("All tests worked~n"), 48 | init:stop(). 49 | ``` 50 | 51 | Compiling and running: 52 | ``` 53 | $ gmake 54 | erlc -W example1.erl 55 | erlc -W unit_test.erl 56 | gcc -o example1 example1.c erl_comm.c example1_driver.c 57 | 58 | Testing drivers 59 | All tests worked 60 | 61 | ~/erlang_programs/chap15$ erl 62 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 63 | Eshell V8.2 (abort with ^G) 64 | 65 | 1> example1:start(). 66 | true 67 | 2> example1:sum(45, 32). 68 | 77 69 | 3> example1:twice(10). 70 | 20 71 | ``` 72 | -------------------------------------------------------------------------------- /Chapter 17/exercise 1.md: -------------------------------------------------------------------------------- 1 | This exercise took me a long time to complete. I think erlang is very hard to debug because of the poor error messages. For instance, at one point I mistyped `HTTP` as `HTPP` in the request, and it took me forever to debug that. I eventually had to look at the binary output of my request and compare it byte for byte with a seemingly identical request that I knew worked. 2 | 3 | While working on the exercise, I found it very helpful to use `curl` to examine the format of the request and the response: 4 | 5 | $ curl -vIF mail.com 6 | 7 | which produces output like this: 8 | ``` 9 | $ curl -vIL mail.com 10 | * Rebuilt URL to: mail.com/ 11 | * Trying 74.208.122.4... 12 | * Connected to mail.com (74.208.122.4) port 80 (#0) 13 | > HEAD / HTTP/1.1 14 | > Host: mail.com 15 | > User-Agent: curl/7.43.0 16 | > Accept: */* 17 | > 18 | < HTTP/1.1 301 Moved Permanently 19 | HTTP/1.1 301 Moved Permanently 20 | < Date: Sun, 28 May 2017 21:44:57 GMT 21 | Date: Sun, 28 May 2017 21:44:57 GMT 22 | < Server: Apache 23 | Server: Apache 24 | < Location: https://www.mail.com/ 25 | Location: https://www.mail.com/ 26 | < Vary: Accept-Encoding 27 | Vary: Accept-Encoding 28 | < Connection: close 29 | Connection: close 30 | < Content-Type: text/html; charset=iso-8859-1 31 | Content-Type: text/html; charset=iso-8859-1 32 | 33 | < 34 | * Closing connection 0 35 | * Issue another request to this URL: 'https://www.mail.com/' 36 | * Trying 74.208.122.4... 37 | * Connected to www.mail.com (74.208.122.4) port 443 (#1) 38 | * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 39 | * Server certificate: *.mail.com 40 | * Server certificate: thawte SSL CA - G2 41 | * Server certificate: thawte Primary Root CA 42 | > HEAD / HTTP/1.1 43 | > Host: www.mail.com 44 | > User-Agent: curl/7.43.0 45 | > Accept: */* 46 | > 47 | < HTTP/1.1 200 OK 48 | HTTP/1.1 200 OK 49 | < Date: Sun, 28 May 2017 21:44:57 GMT 50 | Date: Sun, 28 May 2017 21:44:57 GMT 51 | < Server: Apache 52 | Server: Apache 53 | < Vary: X-Forwarded-Proto,Host,Accept-Encoding 54 | Vary: X-Forwarded-Proto,Host,Accept-Encoding 55 | < Set-Cookie: cookieKID=kid%40autoref%40mail.com; Domain=.mail.com; Expires=Tue, 27-Jun-2017 21:44:57 GMT; Path=/ 56 | Set-Cookie: cookieKID=kid%40autoref%40mail.com; Domain=.mail.com; Expires=Tue, 27-Jun-2017 21:44:57 GMT; Path=/ 57 | < Set-Cookie: cookiePartner=kid%40autoref%40mail.com; Domain=.mail.com; Expires=Tue, 27-Jun-2017 21:44:57 GMT; Path=/ 58 | Set-Cookie: cookiePartner=kid%40autoref%40mail.com; Domain=.mail.com; Expires=Tue, 27-Jun-2017 21:44:57 GMT; Path=/ 59 | < Cache-Control: no-cache, no-store, must-revalidate 60 | Cache-Control: no-cache, no-store, must-revalidate 61 | < Pragma: no-cache 62 | Pragma: no-cache 63 | < Expires: Thu, 01 Jan 1970 00:00:00 GMT 64 | Expires: Thu, 01 Jan 1970 00:00:00 GMT 65 | < Set-Cookie: JSESSIONID=03CBEC8498A0B2D36FCB9E38A5D1DDCE; Path=/mailcom-webapp/; HttpOnly 66 | Set-Cookie: JSESSIONID=03CBEC8498A0B2D36FCB9E38A5D1DDCE; Path=/mailcom-webapp/; HttpOnly 67 | < Content-Language: en-US 68 | Content-Language: en-US 69 | < Content-Length: 84952 70 | Content-Length: 84952 71 | < Connection: close 72 | Connection: close 73 | < Content-Type: text/html;charset=UTF-8 74 | Content-Type: text/html;charset=UTF-8 75 | 76 | < 77 | * Closing connection 1 78 | ``` 79 | 80 | #### Host header 81 | 82 | I used `mail.com` and `google.com` as the hosts for testing--they both redirect. At some point, I decided to convert everything to `HTTP/1.1`. `HTTP/1.1` _requires_ a `Host` header, and I found that even `HTTP/1.0` wouldn't work correctly without a Host header. I read something that said proxies, i.e. something that stands between the client and the host, may require the Host header. 83 | 84 | #### Connection header 85 | 86 | In order to avoid the overhead of setting up a TCP connection every time the client makes a request, `HTTP/1.1` creates a `persistent` TCP connection. The problem with that state of affairs is that the only way\** the client knows that it has read the entire response is if the server closes the socket. 87 | 88 | > HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion of the response. For example, 89 | > 90 | > `Connection: close` 91 | > 92 | > in either the request or the response header fields indicates that the connection SHOULD NOT be considered 'persistent' (section 8.1) after the current request/response is complete. 93 | 94 | So I included a `Connection: close` header in the request. 95 | 96 | #### https urls 97 | 98 | I noticed that `mail.com` (unlike google.com) always redirects to an `https` url. So I learned about ssl sockets, and I used the `ssl module` to open an ssl socket when the redirect was to an `https` url. For info about ssl and erlang, see here: 99 | 100 | http://erlang.org/doc/apps/ssl/using_ssl.html 101 | 102 | --- 103 | 104 | \** I discovered that HTTP/1.1 uses *chunked transfer encoding*. Practically, what that meant for me was that the response had hexadecimal numbers littered throughout. The way that HTTP/1.1 persistent connections work is that the server sends data in chunks and preceding each chunk is the length of the chunk. The tricky part of that is: the chunk the server sends gets split into smaller chunks when it gets transported across a TCP connection to the client, so only some of the chunks that the client reads have a Length at the start of the chunk. Because the server does not close a persistent connection after sending the response, in order to indicate that the response has ended, the server sends 0 for the Length of the next chunk. I decided not to try to deal with persistent sockets and reading the chunk lengths, so if you look at the body of the response in my output, the body is preceded by the hexadecimal chunk length `1ff8`. The simple fix is just to change to HTTP/1.0 in my `format_request()` function. 105 | 106 | --- 107 | 108 | My client requires that it be called with a full url. That's because my client parses the url using `http_uri:parse()` and without the `http` or `https` part, parsing causes an error. Edit: I added a fix for that. Now, I can call client() with a url of the form: `mail.com`. 109 | 110 | ```erlang 111 | -module(c2). 112 | %%-compile(export_all). 113 | -export([client/1, go/0]). 114 | 115 | %%client(Url) -> 116 | %% {ok, Pieces} = http_uri:parse(Url), 117 | %% send_request(Pieces). 118 | 119 | client(Url) -> 120 | case http_uri:parse(Url) of 121 | {ok, Pieces} -> 122 | send_request(Pieces); 123 | {error, no_scheme} -> 124 | client("http://" ++ Url); %%Default to http (v. https) 125 | {error, Other} -> 126 | io:format("~p~n", [Other]) 127 | end. 128 | 129 | send_request({http, [], Host, Port, Path, Query}) -> 130 | {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}, 131 | {packet,0}, {reuseaddr,true}]), 132 | Request = format_request(Host, Path, Query), 133 | io:format("Request:~n~s~n", [Request]), 134 | ok = gen_tcp:send(Socket, Request), 135 | read(Socket, []); 136 | send_request({https, [], Host, Port, Path, Query}) -> 137 | ssl:start(), 138 | {ok, Socket} = ssl:connect(Host, Port, [binary, {active, false}, 139 | {packet,0}, {reuseaddr,true}]), 140 | Request = format_request(Host, Path, Query), 141 | ok = ssl:send(Socket, Request), 142 | read_ssl(Socket, []). 143 | 144 | format_request(Host, Path, Query) -> 145 | %%Host header is required for HTTP/1.1, but HTTP/1.0 won't work without it either. 146 | %%Connection:close is needed because keep alive is the default in HTTP/1.1. 147 | HttpLine = io_lib:format("GET ~s~s HTTP/1.1", [Path, Query] ), 148 | HostHeader = io_lib:format("Host: ~s", [Host]), 149 | UserAgent = "User-Agent: curl/7.43.0", %%I used curl to examine the request: 150 | Accept = "Accept: */*", %% $ curl -ILv mail.com 151 | Connection = "Connection: close", %%and I used the same mimimal headers that curl used. 152 | End = "\r\n", 153 | string:join([HttpLine,HostHeader,UserAgent,Accept,Connection,End], "\r\n"). 154 | 155 | read(Socket, Chunks) -> 156 | case gen_tcp:recv(Socket, 0) of 157 | {ok, Chunk} -> 158 | read(Socket, [Chunk|Chunks]); 159 | {error, closed} -> 160 | gen_tcp:close(Socket), 161 | Response = list_to_binary(lists:reverse(Chunks)), 162 | %%io:format("RESPONSE:~n~s~n", [JoinedBin]), 163 | handle_status(Response) 164 | end. 165 | 166 | read_ssl(Socket, Chunks) -> 167 | case ssl:recv(Socket, 0) of 168 | {ok, Chunk} -> 169 | io:format("~n****Chunk:*****~n~s~~n", [Chunk]), 170 | read_ssl(Socket, [Chunk|Chunks]); 171 | {error, closed} -> 172 | Response = list_to_binary(lists:reverse(Chunks)), 173 | handle_status(Response) 174 | end. 175 | 176 | handle_status(Response)-> 177 | [HeaderSection, _] = binary:split(Response, <<"\r\n\r\n">>), 178 | [HttpLine, Headers] = binary:split(HeaderSection, <<"\r\n">>), 179 | io:format("HttpLine:~n~s~nHeaders:~n~s~n~n", [HttpLine, Headers]), 180 | [_,Status|_] = binary:split(HttpLine, <<" ">>, [global]), 181 | StatusCode = list_to_integer(binary_to_list(Status)), 182 | io:format("StatusCode: ~w~n~n", [StatusCode]), 183 | handle_status(StatusCode, Headers, Response). 184 | 185 | handle_status(Code, _, Response) when Code >= 200, Code < 300 -> 186 | io:format("***Response:***~n~s~n~n", [binary:part(Response, 0, 1000)] ); 187 | handle_status(Code, Headers, _) when Code >= 300, Code < 400 -> 188 | HeaderList = binary:split(Headers, <<"\r\n">>, [global]), 189 | handle_status_300(HeaderList, Code); 190 | handle_status(Code, _, _) -> 191 | {error, {status_code, Code, no_handler}}. 192 | 193 | handle_status_300([Header|Headers], Code) -> 194 | [Name, Value] = binary:split(Header, <<": ">>), 195 | case Name of 196 | <<"Location">> -> 197 | io:format("Location: ~s~n~n", [Value]), 198 | RedirectUrl = Value, 199 | client(binary_to_list(RedirectUrl)); 200 | _ -> 201 | handle_status_300(Headers, Code) 202 | end; 203 | handle_status_300([], Code) -> 204 | {error, {status_code, Code, no_location}}. 205 | 206 | go() -> 207 | %%client("https://www.mail.com"). 208 | %client("mail.com"). 209 | %%io:format("~s~n", [ binary:part(Bin, {0,}) ]). 210 | 211 | %%client("http://www.google.com"). 212 | client("http://www.mail.com"). 213 | 214 | %% Headers = [ 215 | %% "GET / HTTP/1.1", 216 | %% "Host: www.google.com", 217 | %% "User-Agent: curl/7.43.0", 218 | %% "Connection: close", 219 | %% "Accept: */*", 220 | %% "\r\n" 221 | %% ], 222 | %% Host = "www.google.com", 223 | %% Request = string:join(Headers, "\r\n"), 224 | %% client(Host, Request). 225 | %% 226 | 227 | ``` 228 | 229 | In the shell: 230 | ``` 231 | $ ./run 232 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 233 | Eshell V8.2 (abort with ^G) 234 | 235 | 1> Request: 236 | GET / HTTP/1.1 237 | Host: www.mail.com 238 | User-Agent: curl/7.43.0 239 | Accept: */* 240 | Connection: close 241 | 242 | 243 | HttpLine: 244 | HTTP/1.1 301 Moved Permanently 245 | Headers: 246 | Date: Sun, 28 May 2017 12:57:22 GMT 247 | Server: Apache 248 | Location: https://www.mail.com/ 249 | Vary: Accept-Encoding 250 | Content-Length: 229 251 | Connection: close 252 | Content-Type: text/html; charset=iso-8859-1 253 | 254 | StatusCode: 301 255 | 256 | Location: https://www.mail.com/ 257 | 258 | HttpLine: 259 | HTTP/1.1 200 OK 260 | Headers: 261 | Date: Sun, 28 May 2017 12:57:23 GMT 262 | Server: Apache 263 | Vary: X-Forwarded-Proto,Host,Accept-Encoding 264 | Set-Cookie: cookieKID=kid%40autoref%40mail.com; Domain=.mail.com; Expires=Tue, 27-Jun-2017 12:57:23 GMT; Path=/ 265 | Set-Cookie: cookiePartner=kid%40autoref%40mail.com; Domain=.mail.com; Expires=Tue, 27-Jun-2017 12:57:23 GMT; Path=/ 266 | Cache-Control: no-cache, no-store, must-revalidate 267 | Pragma: no-cache 268 | Expires: Thu, 01 Jan 1970 00:00:00 GMT 269 | Set-Cookie: JSESSIONID=AD9CB6103185A8A0ADD619B8754E2717; Path=/mailcom-webapp/; HttpOnly 270 | Content-Language: en-US 271 | Connection: close 272 | Transfer-Encoding: chunked 273 | Content-Type: text/html;charset=UTF-8 274 | 275 | StatusCode: 200 276 | 277 | ***Response:*** 278 | HTTP/1.1 200 OK 279 | Date: Sun, 28 May 2017 12:57:23 GMT 280 | Server: Apache 281 | Vary: X-Forwarded-Proto,Host,Accept-Encoding 282 | Set-Cookie: cookieKID=kid%40autoref%40mail.com; Domain=.mail.com; Expires=Tue, 27-Jun-2017 12:57:23 GMT; Path=/ 283 | Set-Cookie: cookiePartner=kid%40autoref%40mail.com; Domain=.mail.com; Expires=Tue, 27-Jun-2017 12:57:23 GMT; Path=/ 284 | Cache-Control: no-cache, no-store, must-revalidate 285 | Pragma: no-cache 286 | Expires: Thu, 01 Jan 1970 00:00:00 GMT 287 | Set-Cookie: JSESSIONID=AD9CB6103185A8A0ADD619B8754E2717; Path=/mailcom-webapp/; HttpOnly 288 | Content-Language: en-US 289 | Connection: close 290 | Transfer-Encoding: chunked 291 | Content-Type: text/html;charset=UTF-8 292 | 293 | 1ff8 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 18 | receive 19 | {tcp, ClientSocket, WholeMsg} -> 20 | case binary_to_term(WholeMsg) of 21 | {Mod, Func, Args} -> 22 | Reply = apply(Mod, Func, Args), 23 | io:format("Server: result is ~w~n", [Reply]), 24 | gen_tcp:send(ClientSocket, term_to_binary(Reply)); 25 | _Other -> 26 | ok 27 | end; 28 | {tcp_closed, ClientSocket} -> 29 | io:format("Server: client closed socket.~n") 30 | end. 31 | ``` 32 | 33 | Client: 34 | 35 | ```erlang 36 | -module(e2c). 37 | -export([client/3, sum/1, go/0]). 38 | 39 | client(Mod, Func, Args) -> 40 | {ok, Socket} = 41 | gen_tcp:connect(localhost, 33133, [binary, {active,true}, 42 | {packet,4}, {reuseaddr,true}] ), 43 | Request = term_to_binary({Mod, Func, Args}), 44 | gen_tcp:send(Socket, Request), 45 | receive 46 | {tcp, Socket, WholeReply} -> 47 | io:format("Client: received ~w~n", [binary_to_term(WholeReply)]), 48 | gen_tcp:close(Socket); 49 | {tcp_closed, Socket} -> 50 | io:format("Client: server closed the socket.~n") 51 | end. 52 | 53 | sum(Args) -> 54 | sum(Args, 0). 55 | 56 | sum([Arg|Args], Sum) -> 57 | sum(Args, Sum+Arg); 58 | sum([], Sum) -> 59 | Sum. 60 | 61 | go() -> 62 | timer:sleep(500), %%Allow time for shell startup output. 63 | spawn(e2s, server_init, []), 64 | timer:sleep(500), %%Allow time for server to startup. 65 | client(?MODULE, sum, [[1, 2, 3]]). 66 | 67 | ``` 68 | 69 | In the shell: 70 | 71 | ``` 72 | $ ./run 73 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 74 | Eshell V8.2 (abort with ^G) 75 | 76 | 1> Server started on port: 33133 77 | Server: result is 6 78 | Client: received 6 79 | ``` 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Chapter 18/simple_example.md: -------------------------------------------------------------------------------- 1 | Joe Armstrong's **ezwebframe** code no longer works. I asked for help on the erlang-questions forum with some of the errors I was getting, and Joe himself answered and essentially said, "It no longer works. Tough luck." Oh, well. 2 | 3 | Instead, I decided to try [gun](https://github.com/ninenines/gun) as an http client to communicate with a cowboy server using websockets. `gun` has builtin websockets support, and `gun` is maintained by the same person who maintains cowboy. `gun` is for `erlang 18+`, though, so if you need to install a more recent version of erlang, you can use `kerl` or `evm` to install multiple versions of erlang on your system and switch between them as needed. 4 | 5 | To setup cowboy, I followed the cowboy User Guide's [Getting Started](https://ninenines.eu/docs/en/cowboy/2.0/guide/getting_started/) section. Once that was setup correctly, I was at the following prompt: 6 | 7 | `(hello_erlang@127.0.0.1)1>` 8 | 9 | To setup gun, I opened up another terminal window, and the [gun docs](https://github.com/ninenines/gun/blob/master/doc/src/guide/start.asciidoc) say that you can use something called `erlang.mk` and add gun as a dependency to your application. According to the [Erlang.mk docs](https://erlang.mk/guide/getting_started.html#_getting_started_with_otp_releases), if you create something called _a release_, then you can use the command `make run` (or `gmake run`) to compile and execute your code. After reading the Erlang.mk docs, it wasn't clear to me how to run _an application_ or a _library_, so I opted to create _a release_. First, I created a directory for my release, then I downloaded erlang.mk: 10 | 11 | ``` 12 | ~/erlang_programs$ mkdir my_gun && cd $_ 13 | 14 | ~/erlang_programs/my_gun$ curl -O "https:/erlang.mk/erlang.mk" 15 | ``` 16 | 17 | Then I used the following erlang.mk [command](https://erlang.mk/guide/getting_started.html#_getting_started_with_otp_releases) to create a release: 18 | 19 | ``` 20 | ~/erlang_programs/my_gun$ gmake -f erlang.mk bootstrap-lib bootstrap-rel 21 | git clone https://github.com/ninenines/erlang.mk .erlang.mk.build 22 | Cloning into '.erlang.mk.build'... 23 | remote: Counting objects: 7116, done. 24 | remote: Compressing objects: 100% (10/10), done. 25 | remote: Total 7116 (delta 2), reused 6 (delta 1), pack-reused 7105 26 | Receiving objects: 100% (7116/7116), 3.29 MiB | 2.02 MiB/s, done. 27 | Resolving deltas: 100% (4504/4504), done. 28 | if [ -f build.config ]; then cp build.config .erlang.mk.build; fi 29 | cd .erlang.mk.build && gmake 30 | gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/.erlang.mk.build' 31 | export LC_COLLATE=C; \ 32 | awk 'FNR==1 && NR!=1{print ""}1' core/core.mk index/*.mk core/index.mk core/deps.mk plugins/protobuffs.mk core/erlc.mk core/docs.mk core/rel.mk core/test.mk core/compat.mk plugins/asciidoc.mk plugins/bootstrap.mk plugins/c_src.mk plugins/ci.mk plugins/ct.mk plugins/dialyzer.mk plugins/edoc.mk plugins/erlydtl.mk plugins/escript.mk plugins/eunit.mk plugins/proper.mk plugins/relx.mk plugins/shell.mk plugins/syntastic.mk plugins/triq.mk plugins/xref.mk plugins/cover.mk plugins/sfx.mk core/plugins.mk core/deps-tools.mk \ 33 | | sed 's/^ERLANG_MK_VERSION =.*/ERLANG_MK_VERSION = 2017.07.06-1-gff27159/' \ 34 | | sed 's:^ERLANG_MK_WITHOUT =.*:ERLANG_MK_WITHOUT = :' > erlang.mk 35 | gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/.erlang.mk.build' 36 | cp .erlang.mk.build/erlang.mk ./erlang.mk 37 | rm -rf .erlang.mk.build 38 | 39 | ~/erlang_programs/my_gun$ ls 40 | Makefile erlang.mk rel relx.config src 41 | ``` 42 | As you can see, the erlang.mk command that I used created some files and directories. Next, I edited the Makefile to add gun as a dependency: 43 | 44 | ```make 45 | PROJECT = my_gun 46 | PROJECT_DESCRIPTION = New project 47 | PROJECT_VERSION = 0.1.0 48 | 49 | DEPS = gun 50 | 51 | include erlang.mk 52 | ``` 53 | 54 | Then I compiled and executed the release: 55 | 56 | ``` 57 | ~/erlang_programs/my_gun$ gmake run 58 | DEP gun 59 | gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/gun' 60 | DEP cowlib 61 | DEP ranch 62 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib' 63 | DEPEND cowlib.d 64 | ERLC cow_base64url.erl cow_cookie.erl cow_date.erl cow_hpack.erl cow_http.erl cow_http2.erl cow_http_hd.erl cow_http_te.erl cow_mimetypes.erl cow_multipart.erl cow_qs.erl cow_spdy.erl cow_sse.erl cow_uri.erl cow_ws.erl 65 | APP cowlib 66 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib' 67 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/ranch' 68 | DEPEND ranch.d 69 | ERLC ranch.erl ranch_acceptor.erl ranch_acceptors_sup.erl ranch_app.erl ranch_conns_sup.erl ranch_listener_sup.erl ranch_protocol.erl ranch_server.erl ranch_ssl.erl ranch_sup.erl ranch_tcp.erl ranch_transport.erl 70 | APP ranch 71 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/ranch' 72 | DEPEND gun.d 73 | ERLC gun.erl gun_app.erl gun_content_handler.erl gun_data.erl gun_http.erl gun_http2.erl gun_spdy.erl gun_sse.erl gun_sup.erl gun_ws.erl gun_ws_handler.erl 74 | APP gun 75 | GEN rebar.config 76 | gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/gun' 77 | DEPEND my_gun.d 78 | APP my_gun 79 | GEN /Users/7stud/erlang_programs/my_gun/.erlang.mk/relx 80 | ===> Starting relx build process ... 81 | ===> Resolving OTP Applications from directories: 82 | /Users/7stud/erlang_programs/my_gun/ebin 83 | /Users/7stud/erlang_programs/my_gun/deps 84 | /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib 85 | /Users/7stud/erlang_programs/my_gun/apps 86 | ===> Resolved my_gun_release-1 87 | ===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/bin/hooks/builtin/status" 88 | ===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang 89 | ===> release successfully created! 90 | ===> tarball /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/my_gun_release-1.tar.gz successfully created! 91 | Exec: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/erts-8.2/bin/erlexec -boot /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/my_gun_release -mode embedded -boot_var ERTS_LIB_DIR /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/lib -config /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/vm.args -pa -- console 92 | Root: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release 93 | /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release 94 | heart_beat_kill_pid = 41598 95 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 96 | 97 | 98 | =PROGRESS REPORT==== 10-Jul-2017::21:30:00 === 99 | supervisor: {local,sasl_safe_sup} 100 | started: [{pid,<0.352.0>}, 101 | {id,alarm_handler}, 102 | {mfargs,{alarm_handler,start_link,[]}}, 103 | {restart_type,permanent}, 104 | {shutdown,2000}, 105 | {child_type,worker}] 106 | 107 | =PROGRESS REPORT==== 10-Jul-2017::21:30:00 === 108 | supervisor: {local,sasl_sup} 109 | started: [{pid,<0.351.0>}, 110 | {id,sasl_safe_sup}, 111 | {mfargs, 112 | {supervisor,start_link, 113 | [{local,sasl_safe_sup},sasl,safe]}}, 114 | {restart_type,permanent}, 115 | {shutdown,infinity}, 116 | {child_type,supervisor}] 117 | 118 | =PROGRESS REPORT==== 10-Jul-2017::21:30:00 === 119 | supervisor: {local,sasl_sup} 120 | started: [{pid,<0.353.0>}, 121 | {id,release_handler}, 122 | {mfargs,{release_handler,start_link,[]}}, 123 | {restart_type,permanent}, 124 | {shutdown,2000}, 125 | {child_type,worker}] 126 | 127 | =PROGRESS REPORT==== 10-Jul-2017::21:30:00 === 128 | application: sasl 129 | started_at: 'my_gun@127.0.0.1' 130 | 131 | =PROGRESS REPORT==== 10-Jul-2017::21:30:00 === 132 | supervisor: {local,runtime_tools_sup} 133 | started: [{pid,<0.359.0>}, 134 | {id,ttb_autostart}, 135 | {mfargs,{ttb_autostart,start_link,[]}}, 136 | {restart_type,temporary}, 137 | {shutdown,3000}, 138 | {child_type,worker}] 139 | 140 | =PROGRESS REPORT==== 10-Jul-2017::21:30:00 === 141 | application: runtime_tools 142 | started_at: 'my_gun@127.0.0.1' 143 | Eshell V8.2 (abort with ^G) 144 | 145 | (my_gun@127.0.0.1)1> 146 | ``` 147 | At the erlang shell prompt, `(my_gun@127.0.0.1)1>`, you can now call gun functions that send requests to cowboy. However, typing a lot of code in the shell is a pain, which highlights another advantage of using a release: you can create a function containing the gun commands that you want to execute and call that function from the erlang shell. When you issue the command `gmake run`, all the code in the `src` directory of your release will be compiled. So, I created the following file in the `src` directory of my release (I hit `Ctrl+CC` to get out of the erlang shell): 148 | 149 | ```erlang 150 | -module(my). 151 | -compile(export_all). 152 | 153 | get() -> 154 | {ok, _} = application:ensure_all_started(gun), 155 | {ok, ConnPid} = gun:open("localhost", 8080), 156 | {ok, _Protocol} = gun:await_up(ConnPid), 157 | 158 | StreamRef = gun:get(ConnPid, "/"), 159 | 160 | case gun:await(ConnPid, StreamRef) of 161 | {response, fin, _Status, _Headers} -> 162 | no_data; 163 | {response, nofin, _Status, _Headers} -> 164 | {ok, Body} = gun:await_body(ConnPid, StreamRef), 165 | io:format("~s~n", [Body]) 166 | end. 167 | ``` 168 | 169 | I cobbled that code together from the following files in the [gun docs](https://github.com/ninenines/gun/tree/master/doc/src/guide): 170 | ``` 171 | introduction.asciidoc 172 | start.asciidoc 173 | http.asciidoc 174 | ``` 175 | 176 | `my:get()` sends a GET request--nothing to do with websockets yet--to the cowboy server that I setup to listen on port 8080, and the case statement handles the response. Note that `my:get()` sends the GET request to the same route, "/", that the cowboy Getting Started guide already setup a route and handler for. 177 | 178 | Here's the result of compiling and running the release: 179 | 180 | ``` 181 | ~/erlang_programs/my_gun$ gmake run 182 | gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/gun' 183 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib' 184 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib' 185 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/ranch' 186 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/ranch' 187 | GEN rebar.config 188 | gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/gun' 189 | ===> Starting relx build process ... 190 | ===> Resolving OTP Applications from directories: 191 | /Users/7stud/erlang_programs/my_gun/ebin 192 | /Users/7stud/erlang_programs/my_gun/deps 193 | /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib 194 | /Users/7stud/erlang_programs/my_gun/apps 195 | /Users/7stud/erlang_programs/my_gun/_rel 196 | ===> Resolved my_gun_release-1 197 | ===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/bin/hooks/builtin/status" 198 | ===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang 199 | ===> release successfully created! 200 | ===> tarball /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/my_gun_release-1.tar.gz successfully created! 201 | Exec: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/erts-8.2/bin/erlexec -boot /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/my_gun_release -mode embedded -boot_var ERTS_LIB_DIR /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/lib -config /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/vm.args -pa -- console 202 | Root: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release 203 | /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release 204 | heart_beat_kill_pid = 42813 205 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 206 | 207 | 208 | =PROGRESS REPORT==== 10-Jul-2017::21:54:25 === 209 | supervisor: {local,sasl_safe_sup} 210 | started: [{pid,<0.353.0>}, 211 | {id,alarm_handler}, 212 | {mfargs,{alarm_handler,start_link,[]}}, 213 | {restart_type,permanent}, 214 | {shutdown,2000}, 215 | {child_type,worker}] 216 | 217 | =PROGRESS REPORT==== 10-Jul-2017::21:54:25 === 218 | supervisor: {local,sasl_sup} 219 | started: [{pid,<0.352.0>}, 220 | {id,sasl_safe_sup}, 221 | {mfargs, 222 | {supervisor,start_link, 223 | [{local,sasl_safe_sup},sasl,safe]}}, 224 | {restart_type,permanent}, 225 | {shutdown,infinity}, 226 | {child_type,supervisor}] 227 | 228 | =PROGRESS REPORT==== 10-Jul-2017::21:54:25 === 229 | supervisor: {local,sasl_sup} 230 | started: [{pid,<0.354.0>}, 231 | {id,release_handler}, 232 | {mfargs,{release_handler,start_link,[]}}, 233 | {restart_type,permanent}, 234 | {shutdown,2000}, 235 | {child_type,worker}] 236 | 237 | =PROGRESS REPORT==== 10-Jul-2017::21:54:25 === 238 | application: sasl 239 | started_at: 'my_gun@127.0.0.1' 240 | 241 | =PROGRESS REPORT==== 10-Jul-2017::21:54:25 === 242 | supervisor: {local,runtime_tools_sup} 243 | started: [{pid,<0.360.0>}, 244 | {id,ttb_autostart}, 245 | {mfargs,{ttb_autostart,start_link,[]}}, 246 | {restart_type,temporary}, 247 | {shutdown,3000}, 248 | {child_type,worker}] 249 | 250 | =PROGRESS REPORT==== 10-Jul-2017::21:54:25 === 251 | application: runtime_tools 252 | started_at: 'my_gun@127.0.0.1 253 | Eshell V8.2 (abort with ^G) 254 | (my_gun@127.0.0.1)1> 255 | ``` 256 | 257 | At the prompt, I executed `my:get()`: 258 | 259 | ``` 260 | (my_gun@127.0.0.1)1> my:get(). 261 | 262 | =PROGRESS REPORT==== 10-Jul-2017::21:54:27 === 263 | supervisor: {local,inet_gethost_native_sup} 264 | started: [{pid,<0.367.0>},{mfa,{inet_gethost_native,init,[[]]}}] 265 | 266 | =PROGRESS REPORT==== 10-Jul-2017::21:54:27 === 267 | supervisor: {local,kernel_safe_sup} 268 | started: [{pid,<0.366.0>}, 269 | {id,inet_gethost_native_sup}, 270 | {mfargs,{inet_gethost_native,start_link,[]}}, 271 | {restart_type,temporary}, 272 | {shutdown,1000}, 273 | {child_type,worker}] 274 | Hello Erlang! 275 | ok 276 | (my_gun@127.0.0.1)2> 277 | ``` 278 | Okay, on to websockets. Once you establish a `gun <---> cowboy`connection, in order to use websockets you need to send a special upgrade request to a cowboy route of your choosing, and the handler for that route needs to _upgrade_ the connection to a websocket. As a result, you need to add a new route to cowboy, say, "/please_upgrade_to_websocket" and you need to create a handler for that route. A handler is actually a module, and inside the module you are required to define an `init/2` function. You can read about upgrade requests in the gun docs [here](https://github.com/ninenines/gun/blob/master/doc/src/guide/websocket.asciidoc). You can read about cowboy handlers in general [here](https://ninenines.eu/docs/en/cowboy/2.0/guide/handlers/) and websocket handlers in particular [here](https://ninenines.eu/docs/en/cowboy/2.0/guide/ws_handlers/). 279 | 280 | Switch to the terminal window where cowboy is running--the window should be displaying the prompt: 281 | 282 | `(hello_erlang@127.0.0.1)1>` 283 | 284 | You can kill the cowboy server with `Ctrl+CC`. Here's the new cowboy route: 285 | 286 | ***hello_erlang/src/hello_erlang_app.erl*** 287 | ```erlang 288 | -module(hello_erlang_app). 289 | -behaviour(application). 290 | 291 | -export([start/2]). 292 | -export([stop/1]). 293 | 294 | start(_Type, _Args) -> 295 | Dispatch = cowboy_router:compile([ 296 | {'_', [ 297 | {"/", hello_handler, []}, 298 | {"/please_upgrade_to_websocket", myws_handler, []} %<**** NEW ROUTE **** 299 | ]} 300 | ]), 301 | 302 | {ok, _} = cowboy:start_clear(my_http_listener, 303 | [{port, 8080}], 304 | #{env => #{dispatch => Dispatch} } 305 | ), 306 | 307 | hello_erlang_sup:start_link(). 308 | 309 | stop(_State) -> 310 | ok. 311 | ``` 312 | Here's the new route's handler: 313 | 314 | ***hello_erlang/src/myws_handler.erl*** 315 | ```erlang 316 | -module(myws_handler). 317 | -compile(export_all). 318 | 319 | init(Req, State) -> 320 | {cowboy_websocket, Req, State}. %Tells cowboy to setup the websocket. 321 | 322 | websocket_handle({text, Msg}, State) -> %Automatically called when data arrives. 323 | { 324 | reply, 325 | {text, io_lib:format("Server received: ~s", [Msg]) }, 326 | State 327 | }; 328 | websocket_handle(_Other, State) -> %Ignore 329 | {ok, State}. 330 | ``` 331 | 332 | The handler just prepends the text "Server received: " to whatever text arrives through the websocket and sends the new text back to the client. 333 | 334 | Here's the special upgrade request that gun needs to send to cowboy: 335 | 336 | ***~/erlang_programs/my_gun/src/my.erl*** 337 | ```erlang 338 | -module(my). 339 | %-compile(export_all). 340 | -export([get/0, ws/0]). 341 | 342 | %% No changes to get(): 343 | get() -> 344 | {ok, _} = application:ensure_all_started(gun), 345 | {ok, ConnPid} = gun:open("localhost", 8080), 346 | {ok, _Protocol} = gun:await_up(ConnPid), 347 | 348 | StreamRef = gun:get(ConnPid, "/"), 349 | 350 | case gun:await(ConnPid, StreamRef) of 351 | {response, fin, _Status, _Headers} -> 352 | no_data; 353 | {response, nofin, _Status, _Headers} -> 354 | {ok, Body} = gun:await_body(ConnPid, StreamRef), 355 | io:format("~s~n", [Body]) 356 | end. 357 | 358 | %******** NEW CODE BELOW *********** 359 | ws() -> 360 | {ok, _} = application:ensure_all_started(gun), 361 | {ok, ConnPid} = gun:open("localhost", 8080), 362 | {ok, _Protocol} = gun:await_up(ConnPid), 363 | 364 | gun:ws_upgrade(ConnPid, "/please_upgrade_to_websocket"), %<***** SPECIAL UPGRADE REQUEST **** 365 | 366 | receive 367 | {gun_ws_upgrade, ConnPid, ok, Headers} -> 368 | upgrade_success(ConnPid, Headers); 369 | {gun_response, ConnPid, _, _, Status, Headers} -> 370 | exit({ws_upgrade_failed, Status, Headers}); 371 | {gun_error, _ConnPid, _StreamRef, Reason} -> 372 | exit({ws_upgrade_failed, Reason}) 373 | %% More clauses here as needed. 374 | after 1000 -> 375 | exit(timeout) 376 | end, 377 | 378 | gun:shutdown(ConnPid). 379 | 380 | upgrade_success(ConnPid, Headers) -> 381 | io:format("Upgraded ~w. Success!~nHeaders:~n~p~n", 382 | [ConnPid, Headers]). 383 | ``` 384 | 385 | Switch to the cowboy terminal window and restart cowboy: 386 | ``` 387 | ~/erlang_programs/cowboy_apps/hello_erlang$ gmake run 388 | gmake[1]: Entering directory '/Users/7stud/erlang_programs/cowboy_apps/hello_erlang/deps/cowboy' 389 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/cowboy_apps/hello_erlang/deps/cowlib' 390 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/cowboy_apps/hello_erlang/deps/cowlib' 391 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/cowboy_apps/hello_erlang/deps/ranch' 392 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/cowboy_apps/hello_erlang/deps/ranch' 393 | GEN rebar.config 394 | gmake[1]: Leaving directory '/Users/7stud/erlang_programs/cowboy_apps/hello_erlang/deps/cowboy' 395 | ===> Starting relx build process ... 396 | ===> Resolving OTP Applications from directories: 397 | /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/ebin 398 | /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/deps 399 | /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib 400 | /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/apps 401 | /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel 402 | ===> Resolved hello_erlang_release-1 403 | ===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release/bin/hooks/builtin/status" 404 | ===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang 405 | ===> release successfully created! 406 | ===> tarball /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release/hello_erlang_release-1.tar.gz successfully created! 407 | Exec: /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release/erts-8.2/bin/erlexec -boot /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release/releases/1/hello_erlang_release -mode embedded -boot_var ERTS_LIB_DIR /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release/lib -config /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release/releases/1/vm.args -pa -- console 408 | Root: /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release 409 | /Users/7stud/erlang_programs/cowboy_apps/hello_erlang/_rel/hello_erlang_release 410 | heart_beat_kill_pid = 44891 411 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 412 | 413 | 414 | =PROGRESS REPORT==== 10-Jul-2017::22:40:08 === 415 | supervisor: {local,sasl_safe_sup} 416 | started: [{pid,<0.384.0>}, 417 | {id,alarm_handler}, 418 | {mfargs,{alarm_handler,start_link,[]}}, 419 | {restart_type,permanent}, 420 | {shutdown,2000}, 421 | {child_type,worker}] 422 | 423 | =PROGRESS REPORT==== 10-Jul-2017::22:40:08 === 424 | supervisor: {local,sasl_sup} 425 | started: [{pid,<0.383.0>}, 426 | {id,sasl_safe_sup}, 427 | {mfargs, 428 | {supervisor,start_link, 429 | [{local,sasl_safe_sup},sasl,safe]}}, 430 | {restart_type,permanent}, 431 | {shutdown,infinity}, 432 | {child_type,supervisor}] 433 | 434 | =PROGRESS REPORT==== 10-Jul-2017::22:40:08 === 435 | supervisor: {local,sasl_sup} 436 | started: [{pid,<0.385.0>}, 437 | {id,release_handler}, 438 | {mfargs,{release_handler,start_link,[]}}, 439 | {restart_type,permanent}, 440 | {shutdown,2000}, 441 | {child_type,worker}] 442 | 443 | =PROGRESS REPORT==== 10-Jul-2017::22:40:08 === 444 | application: sasl 445 | started_at: 'hello_erlang@127.0.0.1' 446 | 447 | =PROGRESS REPORT==== 10-Jul-2017::22:40:08 === 448 | supervisor: {local,runtime_tools_sup} 449 | started: [{pid,<0.391.0>}, 450 | {id,ttb_autostart}, 451 | {mfargs,{ttb_autostart,start_link,[]}}, 452 | {restart_type,temporary}, 453 | {shutdown,3000}, 454 | {child_type,worker}] 455 | 456 | =PROGRESS REPORT==== 10-Jul-2017::22:40:08 === 457 | application: runtime_tools 458 | started_at: 'hello_erlang@127.0.0.1' 459 | Eshell V8.2 (abort with ^G) 460 | (hello_erlang@127.0.0.1)1> 461 | ``` 462 | Switch to the gun terminal window and restart gun: 463 | ``` 464 | ~/erlang_programs/my_gun$ gmake run 465 | gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/gun' 466 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib' 467 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib' 468 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/ranch' 469 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/ranch' 470 | GEN rebar.config 471 | gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/gun' 472 | ===> Starting relx build process ... 473 | ===> Resolving OTP Applications from directories: 474 | /Users/7stud/erlang_programs/my_gun/ebin 475 | /Users/7stud/erlang_programs/my_gun/deps 476 | /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib 477 | /Users/7stud/erlang_programs/my_gun/apps 478 | /Users/7stud/erlang_programs/my_gun/_rel 479 | ===> Resolved my_gun_release-1 480 | ===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/bin/hooks/builtin/status" 481 | ===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang 482 | ===> release successfully created! 483 | ===> tarball /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/my_gun_release-1.tar.gz successfully created! 484 | Exec: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/erts-8.2/bin/erlexec -boot /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/my_gun_release -mode embedded -boot_var ERTS_LIB_DIR /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/lib -config /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/vm.args -pa -- console 485 | Root: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release 486 | /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release 487 | heart_beat_kill_pid = 45280 488 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 489 | 490 | 491 | =PROGRESS REPORT==== 10-Jul-2017::22:49:12 === 492 | supervisor: {local,sasl_safe_sup} 493 | started: [{pid,<0.353.0>}, 494 | {id,alarm_handler}, 495 | {mfargs,{alarm_handler,start_link,[]}}, 496 | {restart_type,permanent}, 497 | {shutdown,2000}, 498 | {child_type,worker}] 499 | 500 | =PROGRESS REPORT==== 10-Jul-2017::22:49:12 === 501 | supervisor: {local,sasl_sup} 502 | started: [{pid,<0.352.0>}, 503 | {id,sasl_safe_sup}, 504 | {mfargs, 505 | {supervisor,start_link, 506 | [{local,sasl_safe_sup},sasl,safe]}}, 507 | {restart_type,permanent}, 508 | {shutdown,infinity}, 509 | {child_type,supervisor}] 510 | 511 | =PROGRESS REPORT==== 10-Jul-2017::22:49:12 === 512 | supervisor: {local,sasl_sup} 513 | started: [{pid,<0.354.0>}, 514 | {id,release_handler}, 515 | {mfargs,{release_handler,start_link,[]}}, 516 | {restart_type,permanent}, 517 | {shutdown,2000}, 518 | {child_type,worker}] 519 | 520 | =PROGRESS REPORT==== 10-Jul-2017::22:49:12 === 521 | application: sasl 522 | started_at: 'my_gun@127.0.0.1' 523 | 524 | =PROGRESS REPORT==== 10-Jul-2017::22:49:12 === 525 | supervisor: {local,runtime_tools_sup} 526 | started: [{pid,<0.360.0>}, 527 | {id,ttb_autostart}, 528 | {mfargs,{ttb_autostart,start_link,[]}}, 529 | {restart_type,temporary}, 530 | {shutdown,3000}, 531 | {child_type,worker}] 532 | 533 | =PROGRESS REPORT==== 10-Jul-2017::22:49:12 === 534 | application: runtime_tools 535 | started_at: 'my_gun@127.0.0.1' 536 | Eshell V8.2 (abort with ^G) 537 | (my_gun@127.0.0.1)1> 538 | ``` 539 | Now execute my:ws() in the gun terminal window: 540 | ``` 541 | (my_gun@127.0.0.1)1> my:ws(). 542 | 543 | =PROGRESS REPORT==== 10-Jul-2017::22:55:43 === 544 | supervisor: {local,inet_gethost_native_sup} 545 | started: [{pid,<0.367.0>},{mfa,{inet_gethost_native,init,[[]]}}] 546 | 547 | =PROGRESS REPORT==== 10-Jul-2017::22:55:43 === 548 | supervisor: {local,kernel_safe_sup} 549 | started: [{pid,<0.366.0>}, 550 | {id,inet_gethost_native_sup}, 551 | {mfargs,{inet_gethost_native,start_link,[]}}, 552 | {restart_type,temporary}, 553 | {shutdown,1000}, 554 | {child_type,worker}] 555 | Upgraded <0.365.0>. Success! 556 | Headers: 557 | [{<<"connection">>,<<"Upgrade">>}, 558 | {<<"date">>,<<"Tue, 11 Jul 2017 04:55:42 GMT">>}, 559 | {<<"sec-websocket-accept">>,<<"eann9OkRXioYj7N6HzsNILl2/JI=">>}, 560 | {<<"server">>,<<"Cowboy">>}, 561 | {<<"upgrade">>,<<"websocket">>}] 562 | ok 563 | (my_gun@127.0.0.1)2> 564 | ``` 565 | Based on that output, the gun client successfully upgraded the connection to a websocket. At this point, the gun client only detects whether the upgrade request succeeded or not. Let's change that so that the gun client sends some data to the server if the upgrade request succeeds: 566 | 567 | ```erlang 568 | -module(my). 569 | -compile(export_all). 570 | 571 | get() -> 572 | ... 573 | 574 | ws() -> 575 | {ok, _} = application:ensure_all_started(gun), 576 | {ok, ConnPid} = gun:open("localhost", 8080), 577 | {ok, _Protocol} = gun:await_up(ConnPid), 578 | 579 | gun:ws_upgrade(ConnPid, "/websocket"), 580 | receive 581 | {gun_ws_upgrade, ConnPid, ok, Headers} -> 582 | upgrade_success(ConnPid, Headers); 583 | {gun_response, ConnPid, _, _, Status, Headers} -> 584 | exit({ws_upgrade_failed, Status, Headers}); 585 | {gun_error, _ConnPid, _StreamRef, Reason} -> 586 | exit({ws_upgrade_failed, Reason}) 587 | %% More clauses here as needed. 588 | after 1000 -> 589 | exit(timeout) 590 | end, 591 | 592 | gun:shutdown(ConnPid). 593 | 594 | upgrade_success(ConnPid, Headers) -> 595 | io:format("Upgraded ~w. Success!~nHeaders:~n~p~n", 596 | [ConnPid, Headers]), 597 | 598 | %% ****** NEW CODE ******* 599 | gun:ws_send(ConnPid, {text, "It's raining!"}), 600 | 601 | receive 602 | {gun_ws, ConnPid, {text, Msg} } -> 603 | io:format("~s~n", [Msg]) 604 | end. 605 | ``` 606 | 607 | Here are the results: 608 | ``` 609 | ~/erlang_programs/my_gun$ gmake run 610 | gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/gun' 611 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib' 612 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib' 613 | gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/ranch' 614 | gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/ranch' 615 | GEN rebar.config 616 | gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/gun' 617 | DEPEND my_gun.d 618 | ERLC my.erl 619 | APP my_gun 620 | ===> Starting relx build process ... 621 | ===> Resolving OTP Applications from directories: 622 | /Users/7stud/erlang_programs/my_gun/ebin 623 | /Users/7stud/erlang_programs/my_gun/deps 624 | /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib 625 | /Users/7stud/erlang_programs/my_gun/apps 626 | /Users/7stud/erlang_programs/my_gun/_rel 627 | ===> Resolved my_gun_release-1 628 | ===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/bin/hooks/builtin/status" 629 | ===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang 630 | ===> release successfully created! 631 | ===> tarball /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/my_gun_release-1.tar.gz successfully created! 632 | Exec: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/erts-8.2/bin/erlexec -boot /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/my_gun_release -mode embedded -boot_var ERTS_LIB_DIR /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/lib -config /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/vm.args -pa -- console 633 | Root: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release 634 | /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release 635 | heart_beat_kill_pid = 47215 636 | Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 637 | 638 | 639 | =PROGRESS REPORT==== 10-Jul-2017::23:07:24 === 640 | supervisor: {local,sasl_safe_sup} 641 | started: [{pid,<0.353.0>}, 642 | {id,alarm_handler}, 643 | {mfargs,{alarm_handler,start_link,[]}}, 644 | {restart_type,permanent}, 645 | {shutdown,2000}, 646 | {child_type,worker}] 647 | 648 | =PROGRESS REPORT==== 10-Jul-2017::23:07:24 === 649 | supervisor: {local,sasl_sup} 650 | started: [{pid,<0.352.0>}, 651 | {id,sasl_safe_sup}, 652 | {mfargs, 653 | {supervisor,start_link, 654 | [{local,sasl_safe_sup},sasl,safe]}}, 655 | {restart_type,permanent}, 656 | {shutdown,infinity}, 657 | {child_type,supervisor}] 658 | 659 | =PROGRESS REPORT==== 10-Jul-2017::23:07:24 === 660 | supervisor: {local,sasl_sup} 661 | started: [{pid,<0.354.0>}, 662 | {id,release_handler}, 663 | {mfargs,{release_handler,start_link,[]}}, 664 | {restart_type,permanent}, 665 | {shutdown,2000}, 666 | {child_type,worker}] 667 | 668 | =PROGRESS REPORT==== 10-Jul-2017::23:07:24 === 669 | application: sasl 670 | started_at: 'my_gun@127.0.0.1' 671 | 672 | =PROGRESS REPORT==== 10-Jul-2017::23:07:24 === 673 | supervisor: {local,runtime_tools_sup} 674 | started: [{pid,<0.360.0>}, 675 | {id,ttb_autostart}, 676 | {mfargs,{ttb_autostart,start_link,[]}}, 677 | {restart_type,temporary}, 678 | {shutdown,3000}, 679 | {child_type,worker}] 680 | 681 | =PROGRESS REPORT==== 10-Jul-2017::23:07:24 === 682 | application: runtime_tools 683 | started_at: 'my_gun@127.0.0.1' 684 | Eshell V8.2 (abort with ^G) 685 | (my_gun@127.0.0.1)1> 686 | ``` 687 | And: 688 | ``` 689 | (my_gun@127.0.0.1)1> my:ws(). 690 | 691 | =PROGRESS REPORT==== 10-Jul-2017::23:07:28 === 692 | supervisor: {local,inet_gethost_native_sup} 693 | started: [{pid,<0.367.0>},{mfa,{inet_gethost_native,init,[[]]}}] 694 | 695 | =PROGRESS REPORT==== 10-Jul-2017::23:07:28 === 696 | supervisor: {local,kernel_safe_sup} 697 | started: [{pid,<0.366.0>}, 698 | {id,inet_gethost_native_sup}, 699 | {mfargs,{inet_gethost_native,start_link,[]}}, 700 | {restart_type,temporary}, 701 | {shutdown,1000}, 702 | {child_type,worker}] 703 | Upgraded <0.365.0>. Success! 704 | Headers: 705 | [{<<"connection">>,<<"Upgrade">>}, 706 | {<<"date">>,<<"Tue, 11 Jul 2017 05:07:28 GMT">>}, 707 | {<<"sec-websocket-accept">>,<<"RnPQPxQFMHCuzb8SoJtjUjlp558=">>}, 708 | {<<"server">>,<<"Cowboy">>}, 709 | {<<"upgrade">>,<<"websocket">>}] 710 | Server received: It's raining! 711 | ok 712 | 713 | (my_gun@127.0.0.1)2> 714 | ``` 715 | 716 | 717 | 718 | -------------------------------------------------------------------------------- /Chapter 3/exercise 3.md: -------------------------------------------------------------------------------- 1 | ```erlang 2 | 3 | 77> H1 = {house, "3 Maple St"}. 4 | {house,"3 Maple St"} 5 | 6 | 78> H2 = {house, "5 Maple St"}. 7 | {house,"5 Maple St"} 8 | 9 | 79> H3 = {house, "7 Maple St"}. 10 | {house,"7 Maple St"} 11 | 12 | 80> Street = [H1, H2, H3]. 13 | [{house,"3 Maple St"}, 14 | {house,"5 Maple St"}, 15 | {house,"7 Maple St"}] 16 | 17 | 81> [{house, Addr} | Tail] = Street. 18 | [{house,"3 Maple St"}, 19 | {house,"5 Maple St"}, 20 | {house,"7 Maple St"}] 21 | 22 | 82> Addr. 23 | "3 Maple St" 24 | 25 | 83> Tail. 26 | [{house,"5 Maple St"},{house,"7 Maple St"}] 27 | 28 | 84> f(Addr).   %"forget" (or unbind) the Addr variable. Or use a different variable name in the expressions below.             29 | ok 30 | 31 | 85> [_, _, {house, Addr} | [] ] = Street. %This syntax is described on the bottom of p. 38. See below for alternative syntax. 32 | [{house,"3 Maple St"}, 33 | {house,"5 Maple St"}, 34 | {house,"7 Maple St"}] 35 | 36 | 86> Addr. 37 | "7 Maple St" 38 | 39 | %This syntax also seems to work: 40 | 41 | 87> f(Addr). 42 | ok 43 | 44 | 88> [_, _, {house, Addr}] = Street. 45 | [{house,"3 Maple St"}, 46 | {house,"5 Maple St"}, 47 | {house,"7 Maple St"}] 48 | 49 | 89> Addr. 50 | "7 Maple St" 51 | ``` 52 | -------------------------------------------------------------------------------- /Chapter 4/exercise 1.md: -------------------------------------------------------------------------------- 1 | geometry.erl: 2 | 3 | ```erlang 4 | 5 | -module(geometry). 6 | -export([area/1, test/0, perimeter/1]). %Add perimeter() to list of exports. 7 | 8 | test() -> 9 | 4 = area({square, 2}), 10 | 10 = area({rectangle, 5, 2}), 11 | tests_worked. 12 | 13 | area({square, Side}) -> 14 | Side * Side; 15 | area({rectangle, Height, Width}) -> 16 | Height * Width; 17 | area({circle, Radius}) -> 18 | 3.142 * Radius * Radius; 19 | area({right_triangle, Side1, Side2}) -> 20 | 0.5 * Side1 * Side2. 21 | 22 | perimeter({square, Side}) -> 23 | 4 * Side; 24 | perimeter({rectangle, Height, Width}) -> 25 | (2 * Height) + (2 * Width); 26 | perimeter({circle, Radius}) -> 27 | 2 * math:pi() * Radius; 28 | perimeter({right_triangle, Side1, Side2}) -> 29 | Side1 + Side2 + math:sqrt((Side1*Side1) + (Side2*Side2)) 30 | ``` 31 | 32 | In the shell: 33 | 34 | ```erlang 35 | 52> c(geometry). 36 | {ok,geometry} 37 | 38 | 53> geometry:perimeter({square, 2}). 39 | 8 40 | 41 | 54> geometry:perimeter({rectangle, 2, 4}). 42 | 12 43 | 44 | 55> geometry:perimeter({circle, 4}). 45 | 25.132741228718345 46 | 47 | 56> geometry:perimeter({right_triangle, 3, 4}). 48 | 12.0 49 | 50 | 58> geometry:area({circle, 10}). 51 | 314.2 52 | 53 | 59> geometry:area({right_triangle, 3, 4}). 54 | 6.0 55 | 56 | 57 | ``` 58 | -------------------------------------------------------------------------------- /Chapter 4/exercise 2.md: -------------------------------------------------------------------------------- 1 | I used the following functions from the `erlang` module: 2 | 3 | ``` 4 | size/1 Gets the size of a tuple. 5 | element/2 Gets the element of a tuple at the specified index. 6 | ``` 7 | my.erl: 8 | ```erlang 9 | -module(my). 10 | -compile(export_all). %You can use this instead of -export([f/1, g/2, ...]). 11 | 12 | tuple_to_list({}) -> []; 13 | tuple_to_list(T) -> tuple_to_list_acc(size(T), T, []). %Will access the elements of the tuple starting at the largest 14 | tuple_to_list_acc(0, _, Acc) -> Acc; %index and work down to the smallest index. 15 | tuple_to_list_acc(CurrIndex, T, Acc) -> 16 | NewAcc = [element(CurrIndex, T) | Acc], 17 | tuple_to_list_acc(CurrIndex-1, T, NewAcc). 18 | ``` 19 | 20 | In the shell: 21 | 22 | ```erlang 23 | 80> c(my). 24 | {ok,my} 25 | 26 | 81> my:tuple_to_list({1, 2, 3}). 27 | [1,2,3] 28 | 29 | 82> my:tuple_to_list({}). 30 | [] 31 | 32 | 83> my:tuple_to_list({1, true, "hello"}). 33 | [1,true,"hello"] 34 | 35 | 84> my:tuple_to_list({1, true, {2.3, "hello"}}). 36 | [1,true,{2.3,"hello"}] 37 | ``` 38 | 39 | Oh boy...I looked at the exercise again while thinking about list comprehensions: 40 | 41 | ```erlang 42 | -module(my). 43 | -compile(export_all). 44 | 45 | tuple_to_list(Tuple) -> 46 | [ 47 | element(Index, Tuple) || Index <- lists:seq(1, size(Tuple) ) 48 | ]. 49 | 50 | %tuple_to_list({}) -> []; 51 | %tuple_to_list(T) -> tuple_to_list_acc(size(T), T, []). 52 | %tuple_to_list_acc(0, _, Acc) -> Acc; 53 | %tuple_to_list_acc(CurrIndex, T, Acc) -> 54 | % NewAcc = [element(CurrIndex, T) | Acc], 55 | % tuple_to_list_acc(CurrIndex-1, T, NewAcc). 56 | ``` 57 | 58 | Wow, like a boss! My first ever erlang one liner: 59 | ```erlang 60 | tuple_to_list(Tuple) -> [element(Index, Tuple) || Index <- lists:seq(1, size(Tuple) )]. 61 | ``` 62 | In the shell: 63 | 64 | ```erlang 65 | 30> c(my). 66 | {ok,my} 67 | 68 | 31> my:tuple_to_list({1, 2, 3}). 69 | [1,2,3] 70 | 71 | 32> my:tuple_to_list({}). 72 | [] 73 | 74 | 33> my:tuple_to_list({1, true, {2.3, false}, fun(X) -> X*2 end}). 75 | [1,true,{2.3,false},#Fun] 76 | 77 | ``` 78 | 79 | That solution made me realize that a list comprehension is really erlang's version of a for-in loop. 80 | -------------------------------------------------------------------------------- /Chapter 4/exercise 3.md: -------------------------------------------------------------------------------- 1 | Here is a naive implementation of `time_func()`: 2 | 3 | ```erlang 4 | time_func(F) -> 5 | Start = now(), 6 | F(), 7 | End = now(), 8 | [ 9 | element(I, End) - element(I, Start) || I <- lists:seq(1, size(Start) ) 10 | ]. 11 | ``` 12 | 13 | If you have Start and End times like this: 14 | 15 | ``` 16 | Start: {X, Y, 999999} 17 | End: {X, Y+1, 200} 18 | ``` 19 | 20 | i.e. the function F took 201 micro seconds to execute, and then you subtract the elements of the two tuples, you get: 21 | 22 | ``` 23 | End: {X, Y+1, 200} 24 | - Start: {X, Y, 999999} 25 | ------------------------ 26 | {0, 1, -999799} 27 | ``` 28 | 29 | In order to fix the negative term, you need to borrow 1 from the Secs and add it to the the Micros: 30 | ``` 31 |        {0, 0, -999799 + 1*1000000} => {0, 0, 201} 32 | ``` 33 | 34 | Therefore, in order to normalize a timestamp so that all the terms are positive, you have to examine each term produced by the naive solution to see if the term is negative, and if it is, then you have to go to the bigger term on the left and borrow 1 from it. 35 | 36 | Erlang does provide a function called `timer:now_diff` that will neatly subtract two timestamps for you, but because the exercise didn't mention that function in the list of functions that we should to look at, I thought I would try to implement my own function to accomplish the same thing: 37 | 38 | ```erlang 39 | ts_diff(End, Start) -> 40 | fix_times( 41 | [element(I, End) - element(I, Start) || I <- lists:seq(1, size(End))] 42 | ). 43 | 44 | fix_times(L) -> 45 | [T|Ts] = lists:reverse(L), %[Megas, Secs, Micros] => [Micros, Secs, Megas] 46 | Acc = [], 47 | fix_times_acc(T, Ts, Acc). 48 | 49 | fix_times_acc(T1, [T2|Tail], Acc) -> 50 | if 51 | T1 < 0 -> 52 | fix_times_acc(T2-1, Tail, [1000000+T1|Acc]); 53 | true -> 54 | fix_times_acc(T2, Tail, [T1|Acc]) 55 | end; 56 | fix_times_acc(T, [], Acc) -> 57 | [T|Acc]. 58 | 59 | ``` 60 | 61 | Here's how `fix_times()` works in the shell: 62 | 63 | ```erlang 64 | 42> lib_misc:fix_times([0, 1, -999799]). 65 | [0,0,201] 66 | 67 | 43> lib_misc:fix_times([1, -500, -1000]). 68 | [0,999499,999000] %borrow 1 from Secs to fix Micros, which leaves Secs equal to -501, then borrow 1 from Megas to fix Secs. 69 | ``` 70 | 71 | For testing, I created a for-loop function to run `time_func()` on a given function F, a given number of times N: 72 | 73 | ```erlang 74 | for2(F, Arg, N) -> 75 | io:format("~s~n", ["looping..."]), 76 | loop(F, Arg, N). 77 | loop(_, _, 0) -> 78 | done; 79 | loop(F, Arg, N) when N > 0 -> 80 | Result = F(Arg), 81 | io:format("~p~n", [Result]), 82 | loop(F, Arg, N-1). 83 | ``` 84 | 85 | In the following output, you can see negative terms produced by the naive solution: 86 | 87 | ```erlang 88 | 21> c(lib_misc). 89 | 90 | %Define some random function that takes a couple of seconds to execute: 91 | 22> F = fun() -> [X*X || X <- lists:seq(1, 1000000), X rem 2 =:= 0] end. 92 | 93 | %Apparently, in order to pass a named function as an argument to another function 94 | %you have to use the following syntax: fun module:func_name/arity 95 | 23> lib_misc:for2(fun lib_misc:time_func/1, F, 10). 96 | looping... 97 | [0,2,167616] 98 | [0,3,-860245] 99 | [0,2,156090] 100 | [0,2,177153] 101 | [0,2,152959] 102 | [0,2,148198] 103 | [0,2,142560] 104 | [0,3,-840577] 105 | [0,2,164850] 106 | [0,2,238998] 107 | done 108 | ``` 109 | 110 | Here's the modified solution: 111 | 112 | ```erlang 113 | time_func(F) -> 114 | Start = now(), 115 | F(), 116 | End = now(), 117 | ts_diff(End, Start). 118 | 119 | ts_diff(End, Start) -> 120 | fix_times( 121 | [element(I, End) - element(I, Start) || I <- lists:seq(1, size(End))] 122 | ). 123 | 124 | fix_times(L) -> 125 | [T|Ts] = lists:reverse(L), %[Megas, Secs, Micros] => [Micros, Secs, Megas] 126 | Acc = [], 127 | fix_times_acc(T, Ts, Acc). 128 | 129 | fix_times_acc(T, [], Acc) -> 130 | [T|Acc]; 131 | fix_times_acc(T1, [T2|Tail], Acc) -> 132 | if 133 | T1 < 0 -> 134 | fix_times_acc(T2-1, Tail, [1000000+T1|Acc]); 135 | true -> 136 | fix_times_acc(T2, Tail, [T1|Acc]) 137 | end. 138 | ``` 139 | 140 | In the shell: 141 | 142 | ```erlang 143 | 30> c(lib_misc). 144 | 145 | %Define some random function that takes a couple of seconds to execute: 146 | 31> F = fun() -> [X*X || X <- lists:seq(1, 1000000), X rem 2 =:= 0] end. 147 | 148 | %This time run the for-loop 20 times: 149 | 32> lib_misc:for2(fun lib_misc:time_func/1, F, 20). 150 | looping... 151 | [0,2,228709] 152 | [0,2,187678] 153 | [0,2,175273] 154 | [0,2,187329] 155 | [0,2,223996] 156 | [0,2,162220] 157 | [0,2,180352] 158 | [0,2,199191] 159 | [0,2,155705] 160 | [0,2,162073] 161 | [0,2,168187] 162 | [0,2,162762] 163 | [0,2,158665] 164 | [0,2,166814] 165 | [0,2,169264] 166 | [0,2,163484] 167 | [0,2,225341] 168 | [0,2,185394] 169 | [0,2,127794] 170 | [0,2,134793] 171 | done 172 | 24> 173 | ``` 174 | 175 | Note that there are no negative terms anymore. 176 | 177 | Finally, to return a tuple from `ts_diff()` like `timer:now_diff()` does: 178 | 179 | ```erlang 180 | ts_diff(End, Start) -> 181 | Result = fix_times( 182 | [element(I, End) - element(I, Start) || I <- lists:seq(1, size(End))] 183 | ), 184 | list_to_tuple(Result). % ****CHANGE HERE**** 185 | 186 | ``` 187 | 188 | Here's an alternate solution for `time_func()` that converts the Start and End tuples to total microseconds, subtracts them, then converts the result back to a timestamp tuple: 189 | 190 | ```erlang 191 | time_func(F) -> 192 | Start = now(), 193 | F(), 194 | End = now(), 195 | DiffMicros = total_micros(End) - total_micros(Start), 196 | timestamp(DiffMicros, size(End)). 197 | 198 | total_micros(Tuple) -> 199 | TSize = size(Tuple), 200 | Result = [ 201 | element(I, Tuple) * math:pow(1000000, TSize-I) 202 | || I <- lists:seq(1, TSize) 203 | ], 204 | round(lists:sum(Result)). %round() converts to integer. 205 | 206 | timestamp(_, 0) -> 207 | []; 208 | timestamp(Micros, TSize) -> 209 | Units = round(math:pow(1000000, TSize-1)), 210 | UnitsCount = Micros div Units, 211 | NewMicros = Micros rem Units, 212 | %I tried the following methodology(from p. 56) instead of using an Acc: 213 | [UnitsCount|timestamp(NewMicros, TSize-1)]. 214 | ``` 215 | 216 | In the shell, 217 | 218 | ```erlang 219 | 220 | 164> c(lib_misc). 221 | {ok,lib_misc} 222 | 223 | 165> lib_misc:timestamp(100, 3). 224 | [0,0,100] 225 | 226 | 166> lib_misc:timestamp(0, 3). 227 | [0,0,0] 228 | 229 | 167> lib_misc:timestamp(1000000*1000000+1500000, 3). 230 | [1,1,500000] 231 | 232 | %Define some random function that takes a couple of seconds to execute: 233 | 31> F = fun() -> [X*X || X <- lists:seq(1, 1000000), X rem 2 =:= 0] end. 234 | 235 | 168> lib_misc:for2(fun lib_misc:time_func/1, F, 10). 236 | looping... 237 | [0,2,166518] 238 | [0,2,63600] 239 | [0,2,138325] 240 | [0,2,118236] 241 | [0,2,134436] 242 | [0,2,89460] 243 | [0,2,114554] 244 | [0,2,112772] 245 | [0,2,121008] 246 | [0,2,118641] 247 | done 248 | ``` 249 | 250 | ----------------------------- 251 | 252 | Here's my solution for `my_date_string()`: 253 | 254 | ```C 255 | my_date_string() -> 256 | {Y, Mon, D} = date(), 257 | {H, M, S} = time(), 258 | io_lib:format("~w-~w-~w ~w:~2..0w:~2..0w", [Y, Mon, D, H, M, S] ). 259 | ``` 260 | 261 | In the shell: 262 | 263 | ```erlang 264 | 43> c(lib_misc). 265 | {ok,lib_misc} 266 | 267 | 44> lib_misc:my_date_string(). 268 | ["2017",45,"2",45,"7",32,"11",58,"53",58,"54"] 269 | ``` 270 | 271 | What the heck is all that garbage? Well, look at this: 272 | 273 | ```erlang 274 | 45> Now = lib_misc:my_date_string(). 275 | ["2017",45,"2",45,"7",32,"11",58,"55",58,"32"] 276 | 277 | 46> io:format("~s~n", [Now]). 278 | 2017-2-7 11:55:32 279 | ok 280 | ``` 281 | 282 | It's a bit confusing how that works. Let's start with converting the integer 2017: 283 | 284 | ```erlang 285 | 50> io_lib:format("~w", [2017]). 286 | ["2017"] 287 | ``` 288 | 289 | The ***control sequence*** `~w` takes the single integer 2017 and turns it into four characters: '2', '0', '1', '7', which is represented by the list: 290 | 291 | "2017" 292 | 293 | Remember, a string is really a list in erlang, which you can see here: 294 | 295 | ```erlang 296 | 51> [$2, $0, $1, $7]. %using the $ syntax on p. 40 to get the ascii code for each character 297 | "2017" 298 | ``` 299 | 300 | Or, equivalently: 301 | 302 | ```erlang 303 | 53> [50,48,49,55]. 304 | "2017" 305 | ``` 306 | 307 | So `~w` takes a single integer 2017, and converts it into a list of four integers: `[50,48,49,55]`. If ***all*** the integers in a list happen to be ascii codes for printable characters, then the erlang shell prints out the list as a string. If the integers in the list represent characters, then that's the output you want to see; but if the integers in the list happen to be the temperature readings on successive days, than you certainly don't want to see a string! To prevent the shell from converting a list of integers to characters, you can use `~w`: 308 | 309 | ```erlang 310 | 54> X = [50,48,49,55]. 311 | "2017" 312 | 313 | 55> io:format("~w~n", [X]). 314 | [50,48,49,55] 315 | ok 316 | ``` 317 | 318 | After `io_lib:format()` converts the single integer 2017 to a list of four integers [50,48,49,55], `io_lib:format()` returns the result wrapped in a list: 319 | 320 | ["2017"] 321 | 322 | That's equivalent to: 323 | ```erlang 324 | > [[50,48,49,55]]. 325 | ["2017"] 326 | ``` 327 | 328 | Okay, that's one term. Now let's look at the whole result: 329 | 330 | ```erlang 331 | 45> Now = lib_misc:my_date_string(). 332 | ["2017",45,"2",45,"7",32,"11",58,"55",58,"32"] 333 | ``` 334 | 335 | The whole result is equivalent to: 336 | 337 | % "2017" "2" 338 | % V V 339 | [ [50,48,49,55], 45, [50], .... ] 340 | % ^ 341 | % | an integer, which is also the ascii code for '-' 342 | 343 | That looks like a nightmare of nested lists! How do we get a single, flat list out of that? Well, look at what the ***control sequence*** `~s` does to a list of nested lists: 344 | 345 | ```erlang 346 | 57> io:format("~s~n", [ ["hello", 97] ]). %97 is an integer, which is also the ascii code for the character 'a'. 347 | helloa 348 | ok 349 | ``` 350 | 351 | Remember, `["hello", 97]` is equivalent to `[ [104,101,108,108,111], 97]`: 352 | 353 | ```erlang 354 | 59> io:format("~w~n", ["hello"]). %A quick way to get all the ascii codes for a string. 355 | [104,101,108,108,111] 356 | ok 357 | 358 | 60> [ [104,101,108,108,111], 97]. 359 | ["hello",97] 360 | 361 | 62> Nested = [ [104,101,108,108,111], 97]. 362 | ["hello",97] 363 | 364 | 63> io:format("~s~n", [Nested]). 365 | helloa 366 | ok 367 | ``` 368 | 369 | A hah! The control sequence `~s` will take a list of nested lists and concatenate them all into a single string, i.e. a flat list with no nested lists! That is what I used to display the return value of `my_date_string()`. (Edit: Alright, I decided to flatten the list myself so that `my_date_string()` doesn't return a nested list that looks like garbage. My `flatten()` function is at the bottom of the page). 370 | 371 | The control sequences are described in the [io:format/2 docs](http://erlang.org/doc/man/io.html#format-2). The general form of a control sequence is: 372 | 373 | ~Width.Precision.PaddingModControl. 374 | 375 | To represent hours like this: 376 | 377 | 1:09:05 378 | 379 | I used a field Width of 2; nothing for the Precision because I don't think it applies to integers; 0 for the padding; nothing for Mod; and `w` for the Control character, giving me: 380 | 381 | ~2..0w 382 | 383 | If you don't use a formating sequence like that, and the time has single digits in it, you will get output like this: 384 | 385 | 1:9:5 386 | 387 | You can see that by altering `my_date_string()`: 388 | ```C 389 | my_date_string() -> 390 | {Y, Mon, D} = date(), 391 | %{H, M, S} = time(), 392 | io_lib:format("~w-~w-~w ~w:~2..0w:~2..0w", [Y, Mon, D, 1, 9, 5] ). % ***Hard coded single digits for the time**** 393 | 394 | ``` 395 | 396 | I decided I liked the way: 397 | 398 | 1:09:05 399 | 400 | looked better than: 401 | 402 | 01:09:05 403 | 404 | so I didn't use a field width, etc. for the hours. 405 | 406 | --------------------------------- 407 | 408 | `flatten()`: 409 | 410 | ```C 411 | my_date_string() -> 412 | {Y, Month, D} = date(), 413 | {H, M, S} = time(), 414 | Result = io_lib:format("~w-~w-~w ~w:~2..0w:~2..0w", [Y, Month, D, H, M, S] ), 415 | flatten(Result). 416 | 417 | flatten(L) -> 418 | Result = flatten_acc(L, []), 419 | lists:reverse(Result). 420 | 421 | flatten_acc([H|T], Acc) -> 422 | if 423 | is_list(H) -> flatten_acc(T, flatten_acc(H, Acc)); 424 | true -> flatten_acc(T, [H|Acc]) 425 | end; 426 | flatten_acc([], Acc) -> 427 | Acc. 428 | ``` 429 | 430 | In the shell: 431 | ```erlang 432 | 110> c(lib_misc). 433 | {ok,lib_misc} 434 | 435 | 111> lib_misc:my_date_string(). 436 | "2017-2-7 22:09:20" 437 | ``` 438 | 439 | Because the result shown in the shell has quotes around it, that means it's one flat list of integers, and all the integers happen to be indentical to printable ascii codes. To see the actual list: 440 | ```erlang 441 | 112> io:format("~w~n", [lib_misc:my_date_string()]). 442 | [50,48,49,55,45,50,45,55,32,50,50,58,52,56,58,52,55] 443 | ok 444 | ``` 445 | -------------------------------------------------------------------------------- /Chapter 4/exercise 4.md: -------------------------------------------------------------------------------- 1 | Python's `datetime` module? I'm facile with python, and I used to know the ins and outs of the datetime module. It's a vast module. I'm not doing this exercise. I'm sure erlang has fine date/time handling functions. When I need to use them, I'll dig into them. I took a look at erlang's calendar module and called it good. 2 | -------------------------------------------------------------------------------- /Chapter 4/exercise 5.md: -------------------------------------------------------------------------------- 1 | Here are a few different solutions: 2 | 3 | ```erlang 4 | -module(math_functions). 5 | %-export([even1/1, even2/1, even3/1, odd1/1, odd2/1]). 6 | -compile(export_all). 7 | 8 | even1(X) when (X rem 2 =:= 0) -> true; 9 | even1(_) -> false. 10 | 11 | even2(X) -> 12 | if 13 | X rem 2 =:= 0 -> true; 14 | true -> false 15 | end. 16 | 17 | even3(X) -> 18 | case X rem 2 of 19 | 0 -> true; 20 | 1 -> false 21 | end. 22 | 23 | 24 | % odd() can be defined in the same myriad ways as above, plus one additional way: 25 | 26 | odd2(X) -> not even1(X). 27 | ``` 28 | -------------------------------------------------------------------------------- /Chapter 4/exercise 6.md: -------------------------------------------------------------------------------- 1 | ```erlang 2 | filter(F, L) -> 3 | [X || X <- L, F(X)]. 4 | ``` 5 | 6 | In the shell: 7 | ```erlang 8 | 115> c(math_functions). 9 | {ok,math_functions} 10 | 11 | 117> F = fun(X) -> math_functions:even1(X) end. 12 | #Fun 13 | 14 | 118> L = lists:seq(0, 10). 15 | [0,1,2,3,4,5,6,7,8,9,10] 16 | 17 | 119> math_functions:filter(F, L). 18 | [0,2,4,6,8,10] 19 | ``` 20 | 21 | Or, like this: 22 | ```erlang 23 | math_functions:filter(fun math_functions:even1/1, L). 24 | [0,2,4,6,8,10] 25 | ``` 26 | -------------------------------------------------------------------------------- /Chapter 4/exercise 7.md: -------------------------------------------------------------------------------- 1 | ```erlang 2 | split(L) -> 3 | split_acc(L, [], []). 4 | 5 | split_acc([H|T], Evens, Odds) when (H rem 2 =:= 0) -> 6 | split_acc(T, [H|Evens], Odds); 7 | split_acc([H|T], Evens, Odds) -> 8 | split_acc(T, Evens, [H|Odds]); 9 | split_acc([], Evens, Odds) -> 10 | {lists:reverse(Evens), lists:reverse(Odds)}. 11 | ``` 12 | 13 | In the shell: 14 | 15 | ```erlang 16 | 129> c(math_functions). 17 | {ok,math_functions} 18 | 19 | 130> math_functions:split(lists:seq(0, 20) ). 20 | {[0,2,4,6,8,10,12,14,16,18,20],[1,3,5,7,9,11,13,15,17,19]} 21 | 22 | 131> math_functions:split([]). 23 | {[],[]} 24 | 25 | 132> math_functions:split([2, 4]). 26 | {[2,4],[]} 27 | 28 | ``` 29 | -------------------------------------------------------------------------------- /Chapter 5/exercise 1.md: -------------------------------------------------------------------------------- 1 | The json functions mentioned on p.84 don't exist, e.g. `maps:to_json(Map)`, so I installed `rebar3`, and I created an erlang project that specifies `jsx` as a dependency. `jsx` is a third party json handling library for erlang. To get setup with rebar3 and jsx see [here](http://stackoverflow.com/questions/34278982/what-is-the-easiest-way-for-beginners-to-install-a-module). 2 | 3 | Oh boy, file I/O without any guidance from the book! 4 | 5 | Reading configuration files: 6 | 7 | ```erlang 8 | -module(my). 9 | -compile(export_all). 10 | 11 | read_config(Fname) -> 12 | case file:read_file(Fname) of 13 | {ok, Text} -> 14 | jsx:decode(Text, [return_maps]); 15 | {error, Reason} -> 16 | Reason 17 | end. 18 | ``` 19 | 20 | my_config.config: 21 | 22 | ```javascript 23 | { 24 | "log_level": "warn", 25 | "in_memory": true, 26 | "port": 2707, 27 | "runner_dirs": ["/dir1", "/dir2"], 28 | "mappings": {"a": [1, 2, 3], "b": {"name": "Mike", "digits": 123} } 29 | } 30 | ``` 31 | 32 | In the shell: 33 | ```erlang 34 | 29> c(my). 35 | {ok,my} 36 | 37 | 30> ConfigMap = my:read_config("my_config.config"). 38 | #{<<"in_memory">> => true, 39 | <<"log_level">> => <<"warn">>, 40 | <<"mappings">> => #{<<"a">> => [1,2,3], 41 | <<"b">> => #{<<"digits">> => 123,<<"name">> => <<"Mike">>}}, 42 | <<"port">> => 2707, 43 | <<"runner_dirs">> => [<<"/dir1">>,<<"/dir2">>]} 44 | ``` 45 | 46 | Oh boy, dealing with binaries without them having been introduced in the book yet. Well, they look like strings with `<<` and `>>` around them, e.g. `<<"hello">>`. 47 | 48 | Figuring out how to do a sanity check on nested json seemed too difficult to me, so I thought I would start with an easier configuration file: 49 | 50 | ```javascript 51 | { 52 | "log_level": "warn", 53 | "in_memory": true, 54 | "port": 2707, 55 | } 56 | ``` 57 | 58 | My approach was to write a function that accepts the ConfigMap, as well as a SanityMap, where the SanityMap contains keys that match some or all of the keys in the ConfigMap, and the values in the SanityMap are (white) lists of accepted values for that key. For instance, here is a SanityMap I used: 59 | 60 | ```erlang 61 | 48> SanityMap = #{ 62 | 48> <<"log_level">> => [<<"warn">>, <<"debug">>], 63 | 48> <<"in_memory">> => [true, false], 64 | 48> <<"port">> => lists:seq(2700, 2800) 65 | 48> }. 66 | 67 | #{<<"in_memory">> => [true,false], 68 | <<"log_level">> => [<<"warn">>,<<"debug">>], 69 | <<"port">> => [2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710, 70 | 2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722, 71 | 2723,2724,2725|...]} 72 | ``` 73 | 74 | The return value of my `sanity_check()` function is a map with the keys in the ConfigMap and values of true or false to indicate whether they passed the sanity check, e.g.: 75 | 76 | ```erlang 77 | 41> my:sanity_check(SanityMap, ConfigMap). 78 | #{<<"in_memory">> => true,<<"log_level">> => true,<<"port">> => true} 79 | ``` 80 | Here's my sanity_check() function: 81 | 82 | ```erlang 83 | sanity_check(SanityMap, ConfigMap) -> 84 | sanity_check_acc( 85 | maps:keys(ConfigMap), 86 | ConfigMap, 87 | SanityMap, 88 | #{} 89 | ). 90 | 91 | sanity_check_acc([Key|Keys], ConfigMap, SanityMap, AccMap) -> 92 | ConfigVal = maps:get(Key, ConfigMap), 93 | 94 | case SanityMap of %Mimic if-else with pattern matching. 95 | #{Key := WhiteList} -> %Pattern matching maps is broken in erlang 17.5, 96 | sanity_check_acc( %so I used erlang 19.2 to run this, which is only slightly better. 97 | Keys, 98 | ConfigMap, 99 | SanityMap, 100 | AccMap#{Key => lists:member(ConfigVal, WhiteList)} 101 | ); 102 | 103 | _ -> sanity_check_acc( %If the Key in ConfigMap is not in SanityMap, do nothing. 104 | Keys, 105 | ConfigMap, 106 | SanityMap, 107 | AccMap 108 | ) 109 | end; 110 | sanity_check_acc([], _, _, AccMap) -> 111 | AccMap. 112 | ``` 113 | 114 | In the shell: 115 | 116 | ```erlang 117 | 37> c(my). 118 | {ok,my} 119 | 120 | 38> f(ConfigMap). 121 | ok 122 | 123 | 39> ConfigMap = my:read_config("my_config.config"). #{<<"in_memory">> => true, 124 | <<"log_level">> => <<"warn">>, 125 | <<"port">> => 2707} 126 | 127 | 40> SanityMap. 128 | #{<<"in_memory">> => [true,false], 129 | <<"log_level">> => [<<"warn">>,<<"debug">>], 130 | <<"port">> => [2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710, 131 | 2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722, 132 | 2723,2724,2725|...]} 133 | 134 | 41> my:sanity_check(SanityMap, ConfigMap). 135 | #{<<"in_memory">> => true,<<"log_level">> => true,<<"port">> => true} 136 | 137 | 42> SanityMap2 = SanityMap#{<<"log_level">> := [<<"debug">>, <<"info">>]}. 138 | #{<<"in_memory">> => [true,false], 139 | <<"log_level">> => [<<"debug">>,<<"info">>], 140 | <<"port">> => [2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710, 141 | 2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722, 142 | 2723,2724,2725|...]} 143 | 144 | 43> my:sanity_check(SanityMap2, ConfigMap). 145 | #{<<"in_memory">> => true,<<"log_level">> => false,<<"port">> => true} 146 | ``` 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /Chapter 5/exercise 2.md: -------------------------------------------------------------------------------- 1 | My `map_search_pred()` function: 2 | ```erlang 3 | -module(my). 4 | -compile(export_all). 5 | 6 | map_search_pred(Map, Pred) -> 7 | map_search_pred(maps:keys(Map), Pred, Map). 8 | 9 | map_search_pred([Key|Keys], Pred, Map) -> 10 | Val = maps:get(Key, Map), 11 | 12 | case Pred(Key, Val) of 13 | true -> {Key, Val}; 14 | false -> map_search_pred(Keys, Pred, Map) 15 | end; 16 | map_search_pred([], _, _) -> 17 | none. 18 | 19 | ``` 20 | 21 | In the shell: 22 | 23 | ```erlang 24 | 62> c(my). 25 | {ok,my} 26 | 27 | 63> f(Map). 28 | ok 29 | 30 | 64> Map = #{ 31 | 64> 1 => 10, 32 | 64> 2 => 20, 33 | 64> 3 => 3 34 | 64> }. 35 | #{1 => 10,2 => 20,3 => 3} 36 | 37 | 65> Pred = fun(X, Y) -> X =:= Y end. 38 | #Fun 39 | 40 | 68> my:map_search_pred(Map, Pred). 41 | {3,3} 42 | 43 | 69> Pred2 = fun(X, Y) -> X+Y =:= 22 end. 44 | #Fun 45 | 46 | 70> my:map_search_pred(Map, Pred2). 47 | {2,20} 48 | 49 | 71> Pred3 = fun(X, Y) -> X*Y =:= 0 end. 50 | #Fun 51 | 52 | 72> my:map_search_pred(Map, Pred3). 53 | none 54 | ``` 55 | -------------------------------------------------------------------------------- /Chapter 5/exercise 3.md: -------------------------------------------------------------------------------- 1 | I know ruby pretty well, and once again, just like with the python datetime exercise in Chapter 4, this is a vast exercise. A Hash in ruby is equivalent to a map in erlang. One thing you have to know about ruby is that a *block* in ruby is similar to an anonymous function, which is equivalent to a fun in erlang. So ruby methods that take a block are methods that accept an anonymous function as an argument. Here is how to interpret the ruby docs: 2 | 3 | any? [{ |(key, value)| block }] → true or false 4 | 5 | The bit inside the `[ ]` means it's optional when you call the method named `any?`, but let's pretend the optional part is required, giving you 6 | 7 | any? { |(key, value)| block } → true or false 8 | 9 | The stuff inside the `{ }` is known as a block, which is just an anonymous function that will be passed to the method named `any?`. Yes, in ruby a method name can contain the `?` character. The `?` character signals that the method returns true or false. And no, ruby does not use `{}` in place of `()` for method calls. If `any?` took a non-function argument you would call `any?` like this: 10 | 11 | any?(arg1) {|(key, val)| key+val == 10} 12 | 13 | Rather, ruby has a special syntax for sending an anonymous function to a method: you specify a block, denoted with `{ }`, after the method call. 14 | 15 | The part between the pipes: 16 | 17 | |(key, value)| 18 | 19 | is the parameter list for the anonymous function. And `block`here: 20 | 21 | { |(key, value)| block } 22 | 23 | somewhat confusingly is just a standin for some code. The code should return true or false for each key/value pair in the Hash. The return value of `any?` is signified by: 24 | 25 | → true or false 26 | 27 | The `any?` method traverses a Hash looking for an element for which block(Key, Val) returns true, and if found `any?`halts and immediately returns true. If the block never returns true, then `any?` returns false. 28 | 29 | Here is my implementation of Ruby's `Hash#any?` method, which is very similar to the `map_search_pred()` function in Exercise 2: 30 | 31 | ```erlang 32 | -module(my). 33 | -compile(export_all). 34 | 35 | any(Map, Pred) -> any(maps:keys(Map), Map, Pred). 36 | 37 | any([Key|Keys], Map, Pred) -> 38 | Val = maps:get(Key, Map), 39 | 40 | case Pred(Key, Val) of 41 | true -> true; 42 | false -> any(Keys, Map, Pred) 43 | end; 44 | any([], _, _) -> %If the entire Keys list was traversed and Pred never returned true, return false. 45 | false. 46 | ``` 47 | 48 | In the shell: 49 | 50 | ```erlang 51 | 85> f(). 52 | 53 | 86> Map = #{ 54 | 86> 1 => 10, 55 | 86> 2 => 20, 56 | 86> 3 => 3 57 | 86> }. 58 | #{1 => 10,2 => 20,3 => 3} 59 | 60 | 87> c(my). 61 | 62 | 88> Pred1 = fun(X, Y) -> X =:= Y end. 63 | #Fun 64 | 65 | 89> Pred2 = fun(X, Y) -> X+Y =:= 22 end. 66 | #Fun 67 | 68 | 90> Pred3 = fun(X, Y) -> X*Y =:= 0 end. 69 | #Fun 70 | 71 | 93> my:any(Map, Pred1). 72 | true 73 | 74 | 94> my:any(Map, Pred2). 75 | true 76 | 77 | 95> my:any(Map, Pred3). 78 | false 79 | ``` 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Chapter 6/exercise 1.md: -------------------------------------------------------------------------------- 1 | `myfile:read()`: 2 | 3 | ```erlang 4 | -module(myfile). 5 | -compile(export_all). 6 | 7 | read(Fname) -> 8 | case file:read_file(Fname) of 9 | {ok, Bin} -> Bin; 10 | {error, Why} -> 11 | error(lists:flatten(io_lib:format( 12 | "Couldn't read file '~s': ~w", 13 | [Fname, Why] 14 | ))) 15 | end. 16 | ``` 17 | 18 | `io_lib:format()` is to sprintf() as `io:format()` is to printf(), i.e. `io_lib:format()` returns a formatted string rather than sending it to stdout. The problem with `io_lib:format()` is that it returns a list of nested lists, which won't readily display as a string in the shell. You can flatten a nested list with `~s` on output, e.g. `io:format("~s", [FormattedString])`, but error() does not appear to use `~s` when outputting the error message (as far as I can tell error() is using `~p` to output the error message). Luckily, `lists:flatten(DeepList)` will flatten a nested list much like `~s`. 19 | 20 | In the shell: 21 | ```erlang 22 | 10> c(myfile). 23 | {ok,myfile} 24 | 25 | 11> myfile:read("non_existent"). 26 | ** exception error: "Couldn't read file 'non_existent': enoent" 27 | in function myfile:read/1 (myfile.erl, line 8) 28 | 29 | 12> myfile:read("out.txt"). 30 | <<"goodbye mars: \"hello world\"\n">> 31 | ``` 32 | -------------------------------------------------------------------------------- /Chapter 6/exercise 2.md: -------------------------------------------------------------------------------- 1 | On p. 97, the book says that detailed error messages should go to a log file. Great, more file I/O without any instruction! Here is the format for the log file that I settled on: 2 | 3 | ```C 4 | $ cat mylog.log 5 | 2017-2-9 21:56:05 6 | Error: a 7 | Stack trace: 8 | [{e,gen_e,1,[{file,"e.erl"},{line,10}]}, 9 | {e,demo3,0,[{file,"e.erl"},{line,15}]}, 10 | {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,661}]}, 11 | {shell,exprs,7,[{file,"shell.erl"},{line,684}]}, 12 | {shell,eval_exprs,7,[{file,"shell.erl"},{line,639}]}, 13 | {shell,eval_loop,3,[{file,"shell.erl"},{line,624}]}] 14 | --- 15 | 2017-2-9 21:56:06 16 | Error: a 17 | Stack trace: 18 | [{e,gen_e,1,[{file,"e.erl"},{line,10}]}, 19 | {e,demo3,0,[{file,"e.erl"},{line,15}]}, 20 | {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,661}]}, 21 | {shell,exprs,7,[{file,"shell.erl"},{line,684}]}, 22 | {shell,eval_exprs,7,[{file,"shell.erl"},{line,639}]}, 23 | {shell,eval_loop,3,[{file,"shell.erl"},{line,624}]}] 24 | --- 25 | 2017-2-9 21:56:07 26 | Error: a 27 | Stack trace: 28 | [{e,gen_e,1,[{file,"e.erl"},{line,10}]}, 29 | {e,demo3,0,[{file,"e.erl"},{line,15}]}, 30 | {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,661}]}, 31 | {shell,exprs,7,[{file,"shell.erl"},{line,684}]}, 32 | {shell,eval_exprs,7,[{file,"shell.erl"},{line,639}]}, 33 | {shell,eval_loop,3,[{file,"shell.erl"},{line,624}]}] 34 | --- 35 | 36 | 37 | ``` 38 | 39 | Here's the format string I used for outputting to the log file: 40 | 41 | ```erlang 42 | error (might not be something convertible to a string, so I used ~w) 43 | | 44 | V 45 | "~s~nError: ~w~nStack trace:~n~p~n---~n", 46 | ^ ^ 47 | | | 48 | timestamp stacktrace(~p breaks up long lines at sensible places) 49 | ``` 50 | 51 | We had to write a function called `my_date_string()` for Exercise 4 in Chapter 4, and I used that function for the timestamp in the log file. Here is the code: 52 | 53 | ```erlang 54 | -module(e). 55 | -compile(export_all). 56 | -import(lib_misc, [my_date_string/0]). 57 | 58 | gen_e(1) -> a; 59 | gen_e(2) -> throw(a); 60 | gen_e(3) -> exit(a); 61 | gen_e(4) -> {'EXIT', a}; 62 | gen_e(5) -> error(a). 63 | 64 | demo3() -> 65 | 66 | try gen_e(5) 67 | catch 68 | error:X -> 69 | case file:open("mylog.log", [append]) of 70 | {ok, F} -> 71 | %error message to log file: 72 | io:format( %You don't have to deal with binaries with 73 | F, %this verion of io:format() 74 | "~s~nError: ~w~nStack trace:~n~p~n---~n", 75 | [my_date_string(), X, erlang:get_stacktrace()] 76 | ), 77 | file:close(F), 78 | 79 | %error message for return value of this function: 80 | lists:flatten( 81 | io_lib:format("There was an error: ~p", [X]) 82 | ) 83 | end %I had a bitch of time trying to figure out the required 84 | %punctuation for the end of this line and the previous line 85 | end. 86 | 87 | ``` 88 | 89 | In the shell: 90 | ```erlang 91 | 85> c(e). 92 | {ok,e} 93 | 94 | 86> e:demo3(). 95 | "There was an error: a" 96 | 97 | 87> e:demo3(). 98 | "There was an error: a" 99 | 100 | 88> e:demo3(). 101 | "There was an error: a" 102 | ``` 103 | 104 | See the top of this page for the output to the log file. 105 | -------------------------------------------------------------------------------- /Chapter 7/exercise 1.md: -------------------------------------------------------------------------------- 1 | `reverse(Bin)`: 2 | 3 | Constructing a binary is much like constructing a list, but instead of using cons, `|`, to add an element to a binary you use a comma: 4 | 5 | << ItemToAdd, SomeBinary/binary >> 6 | 7 | And when pattern matching binaries, the equivalent of: 8 | 9 | [H|T] 10 | 11 | is: 12 | 13 | <> 14 | 15 | Here's an example in the shell: 16 | ```erlang 17 | 73> f(). 18 | ok 19 | 20 | 74> <> = <<6, 3, 9, 2>>. 21 | <<6,3,9,2>> 22 | 23 | 75> X. 24 | 6 25 | 26 | 76> Rest. 27 | <<3,9,2>> 28 | ``` 29 | 30 | Or, you could do this: 31 | ```erlang 32 | 77> f(). 33 | ok 34 | 35 | 78> <> = <<6, 3, 9, 2>>. 36 | <<6,3,9,2>> 37 | 38 | 79> X. 39 | <<6>> 40 | 41 | 80> Rest. 42 | <<3,9,2>> 43 | ``` 44 | Note that Size is specified in bits, but the *total size* of a segment is actually `Size * unit`, and for the binary Type the default for `unit` is 8, so the total size of X is `1*8 = 8 bits` (the default value for `unit` for the other Types is 1). Another way to think about it is, for the binary type the Size is the number of *bytes* for the segment, and for the other types the Size is the number of *bits*. 45 | 46 | Now, for the exercise: 47 | 48 | ```erlang 49 | -module(bin). 50 | -compile(export_all). 51 | 52 | reverse_bytes(Bin) -> 53 | reverse_bytes(Bin, <<>>). 54 | reverse_bytes(<>, Acc) -> %When matching, if a binary Type is the last segment 55 | reverse_bytes(Rest, <>); %its Size can be omitted, and its default Size will 56 | reverse_bytes(<<>>, Acc) -> %be the rest of the binary that you are matching against. 57 | Acc. 58 | ``` 59 | 60 | Compare that to reversing a list: 61 | 62 | ```erlang 63 | reverse_list(List) -> 64 | reverse_list(List, []). 65 | reverse_list([X|Rest], Acc) -> 66 | reverse_list(Rest, [X|Acc]); 67 | reverse_list([], Acc) -> 68 | Acc. 69 | ``` 70 | 71 | The default Type for a segment is `integer`, and the default Size for `integer` is 8 bits, so the example above is equivalent to: 72 | 73 | ```erlang 74 | reverse_bytes(Bin) -> 75 | reverse_bytes(Bin, <<>>). 76 | reverse_bytes(<>, Acc) -> 77 | reverse_bytes(Rest, <>); 78 | reverse_bytes(<<>>, Acc) -> 79 | Acc. 80 | ``` 81 | 82 | In the shell: 83 | ```erlang 84 | 10> c(bin). 85 | {ok,bin} 86 | 87 | 11> bin:reverse_bytes(<<1, 2, 3, 4>>). 88 | <<4,3,2,1>> 89 | 90 | 12> bin:reverse_bytes(<<>>). 91 | <<>> 92 | 93 | 13> bin:reverse_bytes(<<97, 98, 99>>). 94 | <<"cba">> 95 | ``` 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Chapter 7/exercise 2.md: -------------------------------------------------------------------------------- 1 | It took me many rereadings of this exercise to figure out what it was asking us to do. Suppose the Term argument is a list, look at the output here: 2 | 3 | ```erlang 4 | 35> term_to_binary([1, 2, 3]). 5 | <<131,107,0,3,1,2,3>> 6 | 7 | 36> term_to_binary([1, 2, 3, 4, 5, 6]). 8 | <<131,107,0,6,1,2,3,4,5,6>> 9 | ``` 10 | 11 | When there are more items in the list, the return value of `term_to binary()` is longer . I originally interpreted the exercise to mean that we should store 4 in the first part of the binary, then add 4 bytes to the binary from the return value of `term_to_binary()`, which would effectively chop off some of the return value for a long enough list. 12 | 13 | But the exercise description really means: 14 | 15 | > ...return a binary consisting of a 4-byte header *containing a number N*, followed by N bytes of data... 16 | 17 | In other words, the exercise wants us to get the number of bytes of the binary returned by `term_to_binary()`, put that number into 4 bytes of a result binary, then add the return value of `term_to_binary()` to the result binary. That way, the first 4 bytes will tell you how many bytes contain the data that follows. 18 | 19 | Here's my solution: 20 | ```erlang 21 | term_to_packet(Term) -> 22 | Bin = term_to_binary(Term), 23 | N = byte_size(Bin), 24 | <>. 25 | ``` 26 | The total size of a segment is: `Size * unit`. Here Size is 4, and unit is 8, so the total size is: `4 * 8 = 32 bits`. The pattern also could have been written as: 27 | 28 | <> 29 | 30 | because the default type of a segment is integer, and the default unit for the integer Type is 1, so total size is: `32 * 1 = 32 bits`. 31 | 32 | In the shell: 33 | ```erlang 34 | 50> term_to_binary([1, 2, 3]). 35 | <<131,107,0,3,1,2,3>> 36 | 37 | 162> bin:term_to_packet([1, 2, 3]). 38 | <<0,0,0,7, 131,107,0,3,1,2,3>> %I added a space. 39 | ``` 40 | 41 | If you remove the first 4 bytes of the result, you get: 42 | 43 | ```erlang 44 | 177> c(bin). 45 | {ok,bin} 46 | 47 | 178> f(). 48 | ok 49 | 50 | 179> Result = bin:term_to_packet([1, 2, 3]). 51 | <<0,0,0,7,131,107,0,3,1,2,3>> 52 | 53 | 180> <> = Result. 54 | <<0,0,0,7,131,107,0,3,1,2,3>> 55 | 56 | 181> N. 57 | 7 58 | 59 | 182> Rest. 60 | <<131,107,0,3,1,2,3>> 61 | ``` 62 | 63 | Rest is the return value of `term_to_binary()` that we stored in our *packet*. In this case, it doesn't even matter what the number N is: we can just remove the first 4 bytes, and the rest of the binary is the binary representing the Term. But, imagine if several packets were combined into one binary. In that case, you would need to know how many bytes to read in order to get the binary for one Term. Note that the size N, which is 7, matches the number of the bytes in the binary Rest--as the exercise calls for. 64 | -------------------------------------------------------------------------------- /Chapter 7/exercise 3.md: -------------------------------------------------------------------------------- 1 | ```erlang 2 | packet_to_term(Bin) -> 3 | <> = Bin, 4 | binary_to_term(TermBin). 5 | ``` 6 | 7 | This exercise demonstrates the important point that we can match a value (Len), then use that value as a Size later in the pattern (Ahem! What happened to maps in this regard?) 8 | 9 | In the shell: 10 | ```erlang 11 | 186> c(bin). 12 | {ok,bin} 13 | 14 | 187> f(). 15 | ok 16 | 17 | 188> Packet = bin:term_to_packet([1, 2, 3]). 18 | <<0,0,0,7,131,107,0,3,1,2,3>> 19 | 20 | 189> PacketWithExtra = <>/binary >>. 21 | <<0,0,0,7,131,107,0,3,1,2,3,97,98,99>> 22 | 23 | 190> bin:packet_to_term(PacketWithExtra). 24 | [1,2,3] 25 | 26 | ``` 27 | -------------------------------------------------------------------------------- /Chapter 7/exercise 4.md: -------------------------------------------------------------------------------- 1 | ```erlang 2 | tests() -> 3 | %term_to_packet(): 4 | <<3:4/unit:8, 131,97,10>> = term_to_packet(10), 5 | <<9:4/unit:8, 131,100,0,5,104,101,108,108,111>> = term_to_packet(hello), 6 | <<23:4/unit:8, 131,104,3,100,0,2,104,105,70,64,9,153,153,153, 7 | 153,153,154,107,0,3,1,2,3>> = term_to_packet( {hi, 3.2, [1, 2, 3]} ), 8 | 9 | %packet_to_term(): 10 | 10 = packet_to_term(<<0,0,0,3, 131,97,10>>), 11 | hello = packet_to_term(<<0,0,0,9, 131,100,0,5,104,101,108,108,111>>), 12 | {hi, 3.2, [1, 2, 3]} = packet_to_term(<<0,0,0,23,131,104,3,100,0,2, 13 | 104,105,70,64,9,153,153,153,153,153,154,107,0,3,1,2,3>>), 14 | 15 | %round trip: 16 | [3.12, hello, {abc, 1}] = packet_to_term(term_to_packet([3.12, hello, {abc, 1}])), 17 | <<97, 98, 99>> = packet_to_term(term_to_packet(<<97, 98, 99>>)), 18 | hello = packet_to_term(term_to_packet(hello)), 19 | 20 | tests_passed. 21 | 22 | ``` 23 | -------------------------------------------------------------------------------- /Chapter 7/exercise 5.md: -------------------------------------------------------------------------------- 1 | ```erlang 2 | 3 | reverse_bits(Bin) -> 4 | reverse_bits(Bin, <<>>). 5 | 6 | reverse_bits(<>, Acc) -> 7 | reverse_bits(Rest, << X:1, Acc/bitstring >>); 8 | reverse_bits(<<>>, Acc) -> 9 | Acc. 10 | ``` 11 | 12 | In the shell: 13 | ```erlang 14 | 100> c(bin). 15 | {ok,bin} 16 | 17 | 101> f(). 18 | ok 19 | 20 | 102> X = 2#11010101. 21 | 213 22 | 23 | 103> Bin = <>. 24 | <<"Õ">> 25 | 26 | 104> Result = bin:reverse_bits(Bin). 27 | <<"«">> 28 | 29 | 105> <> = Result. %Extract the integer from the binary. 30 | <<"«">> 31 | 32 | 106> io:format("Y:~8.2.0B~nX:~8.2.0B~n", [Y, X]). 33 | Y:10101011 34 | X:11010101 35 | ok 36 | ``` 37 | -------------------------------------------------------------------------------- /Chapter 7/mp3.md: -------------------------------------------------------------------------------- 1 | I got interested in the example in this chapter about finding the synchronization header in an mp3 file. I wanted to see if the example would work on some random, free mp3 file that I downloaded. There is a [document](http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm) explaining the MPEG sync header format, and it also presents the formula for calculating the frame length(apparently the header is included in the frame length, even though I don't see how the frame length formula accounts for the header). Using the partial code in the book and the treatise as a guide, I hacked up my own function. Unfortunately, I couldn't get it to work (float v. integer problem when calculating the frame length). After borrowing some tricks that I discovered in the [full code for the example](https://github.com/everpeace/programming-erlang-code/blob/master/code/mp3_sync.erl) (which allowed me to get rid of the large map I employed), here is what I ended up with (I left the debugging output in the code): 2 | 3 | ```erlang 4 | -module(mp3). 5 | -compile(export_all). 6 | 7 | sync_pos(Fname) -> 8 | {ok, Bin} = file:read_file(Fname), 9 | find_sync(Bin, 0). 10 | 11 | find_sync(Bin, Pos) -> 12 | case get_header(Bin, Pos) of 13 | {ok, FrameLen1} -> 14 | case get_header(Bin, Pos+FrameLen1) of 15 | {ok, FrameLen2} -> 16 | case get_header(Bin, Pos+FrameLen1+FrameLen2) of 17 | {ok, _ } -> {ok, Pos}; 18 | error -> find_sync(Bin, Pos+1) 19 | end; 20 | error -> 21 | find_sync(Bin, Pos+1) 22 | end; 23 | error -> 24 | find_sync(Bin, Pos+1) 25 | end. 26 | 27 | 28 | get_header(Bin, N) -> 29 | try 30 | {_, << Header:4/binary, _/binary >>} = split_binary(Bin, N), 31 | unpack_header(Header) 32 | catch 33 | error:{badmatch, _} -> error(syncHeaderNotFound); %The only way you get a bad match 34 | _:_ -> error %is if N is less than four bytes 35 | end. %from the end of Bin. 36 | 37 | 38 | unpack_header(<< 2#11111111111:11, B:2, C:2, _D:1, E:4, F:2, G:1, _Bits:9 >>) -> 39 | Version = case B of 40 | 0 -> {2,5}; 41 | 1 -> exit(bad_version); 42 | 2 -> 2; 43 | 3 -> 1 44 | end, 45 | Layer = case C of 46 | 0 -> exit(bad_layer); 47 | 1 -> 3; 48 | 2 -> 2; 49 | 3 -> 1 50 | end, 51 | BitRateIndex = E, 52 | SampleRateIndex = F, 53 | Pad = G, 54 | 55 | BitRate = bit_rate( 56 | BitRateIndex, 57 | Version, 58 | Layer 59 | ), 60 | SampleRate = sample_rate( 61 | SampleRateIndex, 62 | Version 63 | ), 64 | 65 | {ok, frame_length(Layer, BitRate, SampleRate, Pad) }; 66 | 67 | unpack_header(_) -> 68 | error. 69 | 70 | frame_length(Layer, BitRate, SampleRate, Pad) -> 71 | case Layer of 72 | 1 -> 73 | (12*(BitRate div SampleRate) + Pad) * 4; 74 | 75 | _ -> 76 | 144*(BitRate div SampleRate) + Pad 77 | end. 78 | 79 | % *******> Throws error for E = 0000 and E = 1111 <************* 80 | bit_rate(2#0000, _, _) -> 81 | io:format("***free format BitRateIndex: ~4.2.0B~n", [2#0000]), 82 | exit(freeFormatBitRateIndex); 83 | 84 | bit_rate(2#1111, _, _) -> 85 | io:format("***Illegal BitRateIndex: ~4.2.0B~n", [2#1111]), 86 | exit(badBitRateIndex); 87 | 88 | bit_rate(BitRateIndex, 1, 1) -> 89 | BitRate = element( 90 | BitRateIndex, 91 | {32,64,96,128,160,192,224,256,288,320,352,384,416,448} 92 | ), 93 | io:format( 94 | "bitrate for ~4.2.0B, V~w, L~w: ~w~n", 95 | [BitRateIndex, 1, 1, BitRate] 96 | ), 97 | BitRate; 98 | 99 | bit_rate(BitRateIndex, 1, 2) -> 100 | BitRate = element( 101 | BitRateIndex, 102 | {32,48,56,64,80,96,112,128,160,192,224,256,320,384} 103 | ), 104 | io:format( 105 | "bitrate for ~4.2.0B, V~w, L~w: ~w~n", 106 | [BitRateIndex, 1, 2, BitRate] 107 | ), 108 | BitRate; 109 | 110 | bit_rate(BitRateIndex, 1, 3) -> 111 | BitRate = element( 112 | BitRateIndex, 113 | {32,40,48,56,64,80,96,112,128,160,192,224,256,320} 114 | ), 115 | io:format( 116 | "bitrate for ~4.2.0B, V~w, L~w: ~w~n", 117 | [BitRateIndex, 1, 3, BitRate] 118 | ), 119 | BitRate; 120 | 121 | bit_rate(BitRateIndex, 2, 1) -> 122 | BitRate = element( 123 | BitRateIndex, 124 | {32,48,56,64,80,96,112,128,144,160,176,192,224,256} 125 | ), 126 | io:format( 127 | "bitrate for ~4.2.0B, V~w, L~w: ~w~n", 128 | [BitRateIndex, 2, 1, BitRate] 129 | ), 130 | BitRate; 131 | 132 | bit_rate(BitRateIndex, 2, 2) -> 133 | BitRate = element( 134 | BitRateIndex, 135 | {8,16,24,32,40,48,56,64,80,96,112,128,144,160} 136 | ), 137 | io:format( 138 | "bitrate for ~4.2.0B, V~w, L~w: ~w~n", 139 | [BitRateIndex, 2, 2, BitRate] 140 | ), 141 | BitRate; 142 | 143 | %BitRate for MPEG Version 2, Layer3 is equivalent to BitRate for MPEG Version2, Layer2: 144 | bit_rate(BitRateIndex, 2, 3) -> 145 | bit_rate(BitRateIndex, 2, 2); 146 | 147 | %BitRate for MPEG Version 2.5 is equivalent to BitRate for MPEG Version 2: 148 | bit_rate(BitRateIndex, {2,5}, Layer) -> 149 | bit_rate(BitRateIndex, 2, Layer). 150 | 151 | 152 | % **** Throws error for F = 2#11 ********** 153 | sample_rate(2#11, _) -> 154 | io:format("***Illegal SampleRateIndex: ~4.2.0B~n", [2#11]), 155 | exit(badSampleRateIndex); 156 | sample_rate(F, 1) -> 157 | SampleRate = element( F+1, {44100, 48000, 32000} ), 158 | io:format( 159 | "sample rate for 2#~2.2.0B, ~w: ~w~n", 160 | [F, 1, SampleRate] 161 | ), 162 | SampleRate; 163 | sample_rate(F, 2) -> 164 | SampleRate = element(F+1, {22050, 24000, 16000} ), 165 | io:format( 166 | "sample rate for 2#~2.2.0B, ~w: ~w~n", 167 | [F, 2, SampleRate] 168 | ), 169 | SampleRate; 170 | sample_rate(F, {2,5}) -> 171 | SampleRate = element(F+1, {11025, 12000, 8000} ), 172 | io:format( 173 | "sample rate for 2#~2.2.0B, ~w: ~w~n", 174 | [F, 2.5, SampleRate] 175 | ), 176 | SampleRate. 177 | ``` 178 | 179 | In the shell: 180 | 181 | ```erlang 182 | 183 | 64> c(mp3). 184 | {ok,mp3} 185 | 186 | 65> mp3:sync_pos("TheRobbieBoydBand_OhAlaska.mp3"). 187 | bitrate for 0101, V1, L1: 160 188 | sample rate for 2#01, 1: 48000 189 | bitrate for 0101, V1, L1: 160 190 | sample rate for 2#00, 1: 44100 191 | ***free format BitRateIndex: 0000 192 | bitrate for 0100, V1, L1: 128 193 | sample rate for 2#01, 1: 48000 194 | bitrate for 0100, V1, L1: 128 195 | ***Illegal SampleRateIndex: 0011 196 | bitrate for 0100, V1, L1: 128 197 | ***Illegal SampleRateIndex: 0011 198 | bitrate for 1001, V1, L3: 128 199 | sample rate for 2#00, 1: 44100 200 | bitrate for 1001, V1, L3: 128 201 | sample rate for 2#00, 1: 44100 202 | bitrate for 1001, V1, L3: 128 203 | sample rate for 2#00, 1: 44100 204 | {ok,1120} 205 | ``` 206 | 207 | Here's what happens when I look for the sync header in a text file with two lines in it: 208 | 209 | ```erlang 210 | 67> mp3:sync_pos("out.txt"). 211 | ** exception error: syncHeaderNotFound 212 | in function mp3:get_header/2 (mp3.erl, line 30) 213 | in call from mp3:find_sync/2 (mp3.erl, line 9) 214 | ``` 215 | Client code that calls `mp3:sync_pos()` could catch the `syncHeaderNotFound` error, which I think would be useful for preventing the program from crashing if the sync header was never found in the file. 216 | -------------------------------------------------------------------------------- /Chapter 8/exercise 1.md: -------------------------------------------------------------------------------- 1 | ```A: A lot!``` 2 | 3 | I'm too lazy to count the functions exported by the `dict` module: 4 | 5 | ```erlang 6 | -module(my). 7 | -compile(export_all). 8 | 9 | export_count(ModName) -> 10 | length(ModName:module_info(exports) ). 11 | 12 | ``` 13 | 14 | What's a dict? Apparently, maps are supposed to replace dicts, with the benefit of being able to use native syntax instead of function calls, e.g. `M2 = M1#{key := 10}` v. `D2 = dict:store(key, 10, D1)`. 15 | -------------------------------------------------------------------------------- /Chapter 8/exercise 2.md: -------------------------------------------------------------------------------- 1 | --**Which module exports the most functions?** 2 | ```erlang 3 | -module(my). 4 | -compile(export_all). 5 | 6 | most_exports(Modules) -> 7 | most_exports(Modules, #{func_count => 0, module => []} ). 8 | 9 | most_exports([{Module, _} | Modules], MaxMap) -> 10 | #{func_count := Max} = MaxMap, %I wonder which is faster: maps:get() or pattern matching? You should be able to write: MaxMap#{count} 11 | ExportCount = length( Module:module_info(exports) ), 12 | 13 | if 14 | ExportCount > Max -> %then replace func_count and module list in the map... 15 | NewMaxMap = MaxMap#{func_count := ExportCount, module := [Module]}, 16 | most_exports(Modules, NewMaxMap); 17 | 18 | ExportCount =:= Max -> %then add the Module to the module list in the map... 19 | ModuleList = maps:get(module, MaxMap), 20 | NewMaxMap = MaxMap#{module := [Module|ModuleList]}, 21 | most_exports(Modules, NewMaxMap); 22 | 23 | ExportCount < Max -> %then do nothing to the map... 24 | most_exports(Modules, MaxMap) 25 | end; 26 | most_exports([], MaxMap) -> 27 | #{func_count := Max, module := Module} = MaxMap, 28 | {Max, Module}. 29 | 30 | ``` 31 | In case there's a tie, I used a list for the module name in the MaxMap. The middle if-clause handles a tie. 32 | 33 | 34 | In the shell: 35 | ```erlang 36 | 154> c(mod1). 37 | {ok,mod1} 38 | 39 | %-module(mod1). 40 | %-compile(export_all). 41 | % 42 | %t1() -> hello. 43 | %t2() -> world. 44 | %t3() -> goodbye. 45 | 46 | 155> c(mod2). 47 | {ok,mod2} 48 | 49 | %-module(mod2). 50 | %-compile(export_all). 51 | % 52 | %t1() -> a. 53 | %t2() -> b. 54 | %t3() -> c. 55 | 56 | 156> my:most_exports([{mod1, blah}, {mod2, bleh}]). 57 | {5,[mod2,mod1]} % Five? 58 | 59 | 157> mod1:module_info(exports). 60 | [{t1,0},{t2,0},{t3,0},{module_info,0},{module_info,1}] %Ah, I remember the chapter mentioning this-- 61 | %it's at the top of p. 120. 62 | 158> my:most_exports(code:all_loaded()). 63 | {325,[erlang]} 64 | ``` 65 | 66 | --**What is the most common function name that is exported?** 67 | ```erlang 68 | -module(my). 69 | -compile(export_all). 70 | 71 | most_cmn_export(Modules) -> 72 | most_cmn_export(Modules, #{}). 73 | 74 | most_cmn_export([ {Module, _} | Modules ], FuncCountMap) -> 75 | FuncNames = Module:module_info(exports), 76 | NewFuncCountMap = add_names(FuncNames, FuncCountMap), 77 | most_cmn_export(Modules, NewFuncCountMap); 78 | most_cmn_export([], FuncCountMap) -> 79 | %io:format("~p~n", [CountMap]), 80 | get_max(maps:to_list(FuncCountMap), #{count => 0, func_name => []} ). 81 | 82 | add_names([FuncName|FuncNames], FuncCountMap) -> 83 | Count = maps:get(FuncName, FuncCountMap, 0), 84 | NewFuncCountMap = FuncCountMap#{FuncName => Count+1}, 85 | add_names(FuncNames, NewFuncCountMap); 86 | add_names([], FuncCountMap) -> 87 | FuncCountMap. 88 | 89 | 90 | get_max([ {FuncName, FuncCount} | Tail ], MaxMap) -> 91 | Max = maps:get(count, MaxMap), 92 | 93 | if 94 | FuncCount > Max -> 95 | get_max(Tail, MaxMap#{count := FuncCount, func_name := [FuncName]} ); 96 | 97 | FuncCount =:= Max -> 98 | FuncNameList = maps:get(func_name, MaxMap), 99 | get_max(Tail, MaxMap#{func_name := [FuncName|FuncNameList]} ); 100 | 101 | FuncCount < Max -> 102 | get_max(Tail, MaxMap) 103 | end; 104 | get_max([], MaxMap) -> 105 | #{count := Max, func_name := FuncName} = MaxMap, 106 | {Max, FuncName}. 107 | 108 | ``` 109 | 110 | In the shell: 111 | ```erlang 112 | 54> c(my). 113 | {ok,my} 114 | 115 | 55> c(mod1). 116 | {ok,mod1} 117 | 118 | %-module(mod1). 119 | %-compile(export_all). 120 | % 121 | %t1() -> hello. 122 | %t2() -> goodbye. 123 | %t3() -> world. 124 | 125 | 56> c(mod2). 126 | {ok,mod2} 127 | 128 | %-module(mod2). 129 | %-compile(export_all). 130 | % 131 | %t2() -> b. 132 | %t3() -> c. 133 | 134 | 57> my:most_cmn_export([{mod1, blah}, {mod2, bleh}]). 135 | {2,[{t3,0},{t2,0},{module_info,1},{module_info,0}]} 136 | 137 | 58> my:most_cmn_export(code:all_loaded()). 138 | {125,[{module_info,1},{module_info,0}]} 139 | ``` 140 | 141 | The if-expression in this solution is very similar to the if-expression in the previous solution. Therefore, I endeavored to refactor the previous solution to create a generic function that encapsulated the if-expression, then I would be able to call that function again in this solution instead of having to duplicate the code. Here's what I came up with: 142 | 143 | ```erlang 144 | update(MaxMap, NewItem, NewItemCount) -> 145 | #{count := CurrentMax} = MaxMap, 146 | 147 | if 148 | NewItemCount > CurrentMax -> %then replace the count and the item list in the map... 149 | MaxMap#{count := NewItemCount, item := [NewItem]}; 150 | 151 | NewItemCount =:= CurrentMax -> %then add the item to the item list in the map... 152 | ItemList = maps:get(item, MaxMap), 153 | MaxMap#{item := [NewItem|ItemList]}; 154 | 155 | NewItemCount < CurrentMax -> %then do nothing to the map... 156 | MaxMap 157 | end. 158 | ``` 159 | The ```update()``` function requires that a MaxMap use the generic keys: count and item. Here's the previous solution refactored to use the ```update()``` function: 160 | 161 | ```erlang 162 | -module(my). 163 | -compile(export_all). 164 | 165 | most_exports(Modules) -> 166 | most_exports(Modules, #{count => 0, item => []} ). 167 | 168 | most_exports([{Module, _} | Modules], MaxMap) -> 169 | ExportCount = length( Module:module_info(exports) ), 170 | NewMaxMap = update(MaxMap, Module, ExportCount), 171 | most_exports(Modules, NewMaxMap); 172 | most_exports([], MaxMap) -> 173 | #{count := Max, item := Module} = MaxMap, 174 | {Max, Module}. 175 | 176 | 177 | update(MaxMap, NewItem, NewItemCount) -> 178 | #{count := CurrentMax} = MaxMap, 179 | 180 | if 181 | NewItemCount > CurrentMax -> %then replace the count and the item list in the map... 182 | MaxMap#{count := NewItemCount, item := [NewItem]}; 183 | 184 | NewItemCount =:= CurrentMax -> %then add the item to the item list in the map... 185 | ItemList = maps:get(item, MaxMap), 186 | MaxMap#{item := [NewItem|ItemList]}; 187 | 188 | NewItemCount < CurrentMax -> %then do nothing to the map... 189 | MaxMap 190 | end. 191 | ``` 192 | 193 | Now, I can employ the `update()` function in this solution: 194 | 195 | ```erlang 196 | -module(my). 197 | -compile(export_all). 198 | 199 | most_cmn_export(Modules) -> 200 | FuncCountMap = create_func_count_map(Modules, #{}), 201 | FuncCountList = maps:to_list(FuncCountMap), 202 | get_max(FuncCountList, #{count => 0, item => []}). 203 | 204 | create_func_count_map([ {Module, _} | Modules ], FuncCountMap) -> 205 | FuncNames = Module:module_info(exports), 206 | NewFuncCountMap = add_names(FuncNames, FuncCountMap), 207 | create_func_count_map(Modules, NewFuncCountMap); 208 | create_func_count_map([], FuncCountMap) -> 209 | %io:format("~p~n", [FuncCountMap]), 210 | FuncCountMap. 211 | 212 | add_names([FuncName|FuncNames], FuncCountMap) -> 213 | FuncCount = maps:get(FuncName, FuncCountMap, 0), 214 | NewFuncCountMap = FuncCountMap#{FuncName => FuncCount+1}, 215 | add_names(FuncNames, NewFuncCountMap); 216 | add_names([], FuncCountMap) -> 217 | FuncCountMap. 218 | 219 | get_max([ {FuncName, FuncCount} | Tuples ], MaxMap) -> 220 | NewMaxMap = update(MaxMap, FuncName, FuncCount), 221 | get_max(Tuples, NewMaxMap); 222 | get_max([], MaxMap) -> 223 | #{count := Max, item := FuncName} = MaxMap, 224 | {Max, FuncName}. 225 | 226 | 227 | ``` 228 | 229 | --**Which functions are unique?** 230 | 231 | ```erlang 232 | -module(my). 233 | -compile(export_all). 234 | 235 | unique_funcs(Modules) -> 236 | FuncCountMap = create_func_count_map(Modules, #{}), 237 | Uniques = maps:filter( 238 | fun(_FuncName, Count) -> Count =:= 1 end, 239 | FuncCountMap 240 | ), 241 | maps:keys(Uniques). 242 | 243 | ``` 244 | 245 | In the shell: 246 | ```erlang 247 | 29> c(my). 248 | {ok,my} 249 | 250 | 30> c(mod1). 251 | {ok,mod1} 252 | 253 | %-module(mod1). 254 | %-compile(export_all). 255 | 256 | %t1() -> hello. 257 | %t2() -> world. 258 | %t3() -> goodbye. 259 | 260 | 31> c(mod2). 261 | {ok,mod2} 262 | 263 | %-module(mod2). 264 | %-compile(export_all). 265 | 266 | %t2() -> b. 267 | %t3() -> c. 268 | 269 | 32> my:unique_funcs([{mod1, blah}, {mod2, bleh}]). 270 | [{t1,0}] 271 | 272 | 33> my:unique_funcs(code:all_loaded()). 273 | [{prim_init,0}, 274 | {controlling_process,2}, 275 | {c_map_pair_exact,2}, 276 | {ann_c_seq,3}, 277 | {parse_address,1}, 278 | ... 279 | ... 280 | {...}|...] 281 | ``` 282 | 283 | Or, using a list comprehension: 284 | ```erlang 285 | unique_funcs(Modules) -> 286 | FuncCountMap = create_func_count_map(Modules, #{}), 287 | [ 288 | FuncName || {FuncName, Count} <- maps:to_list(FuncCountMap), 289 | Count =:= 1 290 | ]. 291 | ``` 292 | 293 | 294 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /Chapter 9/exercise 1.md: -------------------------------------------------------------------------------- 1 | #### *Write some modules that produce dialyzer errors:* 2 | 3 | 4 | ```erlang 5 | -module(dia3). 6 | -export([add/2]). 7 | -include_lib("eunit/include/eunit.hrl"). 8 | 9 | 10 | -spec add(X,Y) -> Result when 11 | X :: integer(), 12 | Y :: integer(), 13 | Result :: integer(). 14 | 15 | add(X,Y) -> 16 | X/Y. %The result is a float. 17 | 18 | ``` 19 | 20 | Running dialyzer: 21 | ``` 22 | ~/erlang_programs$ dialyzer dia3.erl 23 | -----Now, do some Erlang for great Good!------ 24 | 25 | Checking whether the PLT /Users/7stud/.dialyzer_plt is up-to-date... yes 26 | Proceeding with analysis... 27 | dia3.erl:6: Invalid type specification for function dia3:add/2. 28 | The success typing is (number(),number()) -> float() 29 | Unknown functions: 30 | eunit:test/1 31 | done in 0m0.50s 32 | done (warnings were emitted) 33 | ``` 34 | 35 | ------------ 36 | ```erlang 37 | -module(dia4). 38 | -export([f/2]). 39 | 40 | 41 | -spec f(String1, String2) -> Result when 42 | String1 :: string(), 43 | String2 :: string(), 44 | Result :: pos_integer(). 45 | 46 | f(X, Y) -> 47 | %lists:length(X) + lists:length(Y). 48 | X+Y. 49 | ``` 50 | 51 | Running dialyzer: 52 | 53 | ``` 54 | ~/erlang_programs$ dialyzer dia4.erl 55 | -----Now, do some Erlang for great Good!------ 56 | 57 | Checking whether the PLT /Users/7stud/.dialyzer_plt is up-to-date... yes 58 | Proceeding with analysis... 59 | dia4.erl:5: Invalid type specification for function dia4:f/2. 60 | The success typing is (number(),number()) -> number() 61 | done in 0m0.52s 62 | done (warnings were emitted) 63 | 64 | ``` 65 | 66 | dialyzer misses a type error here: 67 | ```erlang 68 | -module(dia4). 69 | -export([f/2]). 70 | 71 | -spec f(String1, String2) -> Result when 72 | String1 :: string(), 73 | String2 :: string(), 74 | Result :: pos_integer(). 75 | 76 | f(X, Y) -> 77 | length(X) + length(Y). 78 | ``` 79 | 80 | Running dialyzer: 81 | ``` 82 | ~/erlang_programs$ dialyzer dia4.erl 83 | -----Now, do some Erlang for great Good!------ 84 | 85 | Checking whether the PLT /Users/7stud/.dialyzer_plt is up-to-date... yes 86 | Proceeding with analysis... done in 0m0.53s 87 | done (passed successfully) 88 | ``` 89 | 90 | Because empty lists have a length of 0, it's possible for `f()` to return 0, which is not a `pos_integer()`. If I comment out the type specification, then run typer, typer will correctly deduce the type of f(): 91 | ``` 92 | ~/erlang_programs$ typer dia4.erl 93 | -----Now, do some Erlang for great Good!------ 94 | 95 | 96 | %% File: "dia4.erl" 97 | %% ---------------- 98 | -spec f([any()],[any()]) -> non_neg_integer(). 99 | ``` 100 | -------------------------------------------------------------------------------- /Chapter 9/exercise 3.md: -------------------------------------------------------------------------------- 1 | #### *Why should you write types first?* 2 | 3 | There seem to be some blah, blah, blah reasons here: 4 | 5 | http://tomasp.net/blog/type-first-development.aspx/ 6 | 7 | 8 | #### *Is this always a good idea?* 9 | 10 | Never. 11 | -------------------------------------------------------------------------------- /Chapter 9/exercise 4.md: -------------------------------------------------------------------------------- 1 | #### *Opaque types:* 2 | 3 | ```erlang 4 | 5 | -module(a). 6 | -export([people/0, get_names/1]). 7 | -export_type([person/0]). 8 | 9 | -opaque person() :: {person, first_name()}. 10 | -type first_name() :: string(). 11 | 12 | -spec people() -> People when 13 | People :: [person()]. 14 | 15 | people() -> 16 | [{person, "Joe"}, {person, "Cindy"}]. 17 | 18 | -spec get_names(People) -> Names when 19 | People :: [person()], 20 | Names :: [string()]. 21 | 22 | get_names(Ps) -> 23 | get_names(Ps, []). 24 | 25 | get_names([ {person, Name} | Ps ], Names) -> 26 | get_names(Ps, [Name|Names]); 27 | get_names([], Names) -> 28 | lists:reverse(Names). 29 | ``` 30 | 31 | ```erlang 32 | -module(b). 33 | -export([do/0]). 34 | 35 | -spec do() -> Name when 36 | Name :: string(). 37 | 38 | do() -> 39 | [{person, Name} | _People] = a:people(), 40 | Name. 41 | ``` 42 | 43 | #### *dialyzer:* 44 | 45 | ``` 46 | ~/erlang_programs$ dialyzer a.erl b.erl 47 | -----Now, do some Erlang for great Good!------ 48 | 49 | Checking whether the PLT /Users/7stud/.dialyzer_plt is up-to-date... yes 50 | Proceeding with analysis... 51 | b.erl:7: Function do/0 has no local return 52 | b.erl:8: The attempt to match a term of type a:person() against the 53 | pattern {'person', Name} breaks the opaqueness of the term 54 | done in 0m0.50s 55 | done (warnings were emitted) 56 | ``` 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solutions for the Exercises in Programming Erlang 2 | Programming Erlang (2nd Ed.) Joe Armstrong 3 | 4 | **Erlang 17.5** (The book was written for that version, so that's the version I'm using.) 5 | 6 | In chapter 13, I got tired of typing commands in the erlang shell to compile and run tests for my programs, so I created a shell script to compile and test my erlang programs with one command: 7 | ``` 8 | $ ./run 9 | ``` 10 | See the Chapter 13 directory for [details](https://github.com/7stud/Programming-Erlang-Exercises-Solutions-Answers/blob/master/Chapter%2013/_running_programs.md). 11 | 12 | If you have a better or different answer click on Issues and post your answer. Anyone looking for alternative answers should click on Issues under each chapter. If you have a question about one of the solutions, post the question in an issue. While I'm working my way through the book, I'll check back regularly. After that, not so much. 13 | --------------------------------------------------------------------------------