Programming Erlang, 2 e., Chapter 2, Exercise 4, put_file()

59 views
Skip to first unread message

David Christensen

unread,
Dec 31, 2021, 8:13:48 AM12/31/21
to erlang-q...@erlang.org
erlang-questions:

I am working my way through "Programming Erlang", 2 e.:

2021-12-30 20:00:05 dpchrist@tinkywinky ~/sandbox/erlang
$ cat /etc/debian_version ; uname -a ; dpkg-query -W erlang
9.13
Linux tinkywinky 4.9.0-17-amd64 #1 SMP Debian 4.9.290-1 (2021-12-12)
x86_64 GNU/Linux
erlang 1:19.2.1+dfsg-2+deb9u3


Chapter 2, Exercise 4, requests that I add a "put_file" command to the
file client and server code. RTFM I found the file:write_file()
function. I can test it with the Erlang shell, and it works:

2021-12-30 20:29:34 dpchrist@tinkywinky ~/sandbox/erlang
$ erl
Erlang/OTP 19 [erts-8.2.1] [source] [64-bit] [smp:8:8]
[async-threads:10] [kernel-poll:false]

Eshell V8.2.1 (abort with ^G)
1> file:write_file("foo.2", "this is foo.2\n").
ok
2> halt().

2021-12-30 20:30:09 dpchrist@tinkywinky ~/sandbox/erlang
$ cat foo.2
this is foo.2


I have extended afile_server.erl with a "put_file" command that calls
file::write_file():

2021-12-30 20:34:25 dpchrist@tinkywinky ~/sandbox/erlang
$ cat ex0204_server.erl
-module(ex0204_server).
-export([start/1, loop/1]).

start(Dir) -> spawn(afile_server, loop, [Dir]).

loop(Dir) ->
receive
{Client, list_dir} ->
Client ! {self(), file:list_dir(Dir)};
{Client, {get_file, File}} ->
Full = filename:join(Dir, File),
Client ! {self(), file:read_file(Full)};
{Client, {put_file, File, Bytes}} ->
Full = filename:join(Dir, File),
Client ! {self(), file:write_file(Full, Bytes)}
end,
loop(Dir).


Testing in the Erlang shell, start() works, the "list_dir" command and
reply work, and the "get_file" command and reply work, but the shell
hangs when I attempt to receive a reply for the "put_file" command:

2021-12-30 20:35:23 dpchrist@tinkywinky ~/sandbox/erlang
$ erl
Erlang/OTP 19 [erts-8.2.1] [source] [64-bit] [smp:8:8]
[async-threads:10] [kernel-poll:false]

Eshell V8.2.1 (abort with ^G)
1> c(ex0204_server).
{ok,ex0204_server}
2> Server = ex0204_server:start(".").
<0.64.0>
3> Server ! {self(), list_dir}.
{<0.57.0>,list_dir}
4> receive A -> A end.
{<0.64.0>,
{ok,[".ex0204_client.erl.swp","Makefile","afile_client.erl",
"afile_server.beam","ex0204_server.erl","hello.beam",
"ex0204_client.erl","hello.erl","afile_server.erl","CVS",
"afile_server.run","ex0204_server.beam"]}}
5> Server ! {self(), {get_file, "hello.erl"}}.
{<0.57.0>,{get_file,"hello.erl"}}
6> receive B -> B end.
{<0.64.0>,
{ok,<<"-module(hello).\n-export([start/0]).\n\nstart() ->\n
io:format(\"hello, world!~n\").\n">>}}
7> Server ! {self(), {put_file, "foo.txt", "foo on you!\n"}}.
{<0.57.0>,{put_file,"foo.txt","foo on you!\n"}}
8> receive C -> C end.

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
a


Why?


David

Leonard B

unread,
Dec 31, 2021, 8:32:20 AM12/31/21
to David Christensen, Erlang/OTP discussions
Compare the receive pattern for "{Client, {put_file, File, Bytes}}" to what you're sending

Leonard B

unread,
Dec 31, 2021, 8:47:54 AM12/31/21
to David Christensen, Erlang/OTP discussions
Sorry, replied from mobile and had issues reading the code.

What I do see is you're spawning a process using a *different module*
(probably a previous file you were working on).

start(Dir) -> spawn(afile_server, loop, [Dir]).

^^ that should probably be

start(Dir) -> spawn(ex0204_server, loop, [Dir]).

Kind regards,
Leonard

David Christensen

unread,
Dec 31, 2021, 5:03:25 PM12/31/21
to erlang-q...@erlang.org
> On Fri, Dec 31, 2021, 08:13 David Christensen <dpch...@holgerdanske.com>
> wrote:
>
>> erlang-questions:
>>
>> I am working my way through "Programming Erlang", 2 e.:
>>
>> 2021-12-30 20:00:05 dpchrist@tinkywinky ~/sandbox/erlang
>> $ cat /etc/debian_version ; uname -a ; dpkg-query -W erlang
>> 9.13
>> Linux tinkywinky 4.9.0-17-amd64 #1 SMP Debian 4.9.290-1 (2021-12-12)
>> x86_64 GNU/Linux
>> erlang 1:19.2.1+dfsg-2+deb9u3
>>
>>
>> Chapter 2, Exercise 4, requests that I add a "put_file" command to the
>> file client and server code.

>> I have extended afile_server.erl with a "put_file" command that calls
>> file::write_file():


On 12/31/21 5:31 AM, Leonard B wrote:
> Compare the receive pattern for "{Client, {put_file, File, Bytes}}" to
> what you're sending


Ignoring the cut-and-paste error that you identified below, this server
code is sending a message to the client:

Client ! {self(), file:write_file(Full, Bytes)}


E.g. a tuple consisting of the client PID and the return value of
write_file().


RTFM file(3erl) says:

write_file(Filename, Bytes) -> ok | {error, Reason}


So, the message sent should be either the tuple:

{self(), ok}


Or the tuple:

{self(), {error, Reason}}


The client (shell) is attempting to receive via this code:

receive C -> C end.


AIUI the receive pattern is a single variable, C, which should match a
single value or a tuple.


So, after receiving, the client value of C should be either:

{<0.64.0>, ok}


Or:

{<0.64.0>, {error, Reason}}


Therefore, I believe the receive pattern (a variable) matches what is
being sent (a tuple).


On 12/31/21 5:47 AM, Leonard B wrote:
> Sorry, replied from mobile and had issues reading the code.
>
> What I do see is you're spawning a process using a*different module*
> (probably a previous file you were working on).
>
> start(Dir) -> spawn(afile_server, loop, [Dir]).
>
> ^^ that should probably be
>
> start(Dir) -> spawn(ex0204_server, loop, [Dir]).
>
> Kind regards,
> Leonard


Thank you for identifying the cut-and-paste error in ex0204_server.erl.
The first argument to spawn() is wrong:

start(Dir) -> spawn(afile_server, loop, [Dir]).


The code should be:

start(Dir) -> spawn(ex0204_server, loop, [Dir]).


Here is the corrected code and a sample run:

2021-12-31 08:38:52 dpchrist@tinkywinky ~/sandbox/erlang
$ cat ex0204_server.erl
-module(ex0204_server).
-export([start/1, loop/1]).

start(Dir) -> spawn(ex0204_server, loop, [Dir]).

loop(Dir) ->
receive
{Client, list_dir} ->
Client ! {self(), file:list_dir(Dir)};
{Client, {get_file, File}} ->
Full = filename:join(Dir, File),
Client ! {self(), file:read_file(Full)};
{Client, {put_file, File, Bytes}} ->
Full = filename:join(Dir, File),
Client ! {self(), file:write_file(Full, Bytes)}
end,
loop(Dir).

2021-12-31 08:39:45 dpchrist@tinkywinky ~/sandbox/erlang
$ erl
Erlang/OTP 19 [erts-8.2.1] [source] [64-bit] [smp:8:8]
[async-threads:10] [kernel-poll:false]

Eshell V8.2.1 (abort with ^G)
1> c(ex0204_server).
{ok,ex0204_server}
2> Server = ex0204_server:start(".").
<0.64.0>
3> Server ! {self(), list_dir}.
{<0.57.0>,list_dir}
4> receive A -> A end.
{<0.64.0>,
{ok,["Makefile",".ex0204_server.erl.swp","afile_client.erl",
"ex0204_server.erl","ex0204_client.erl","hello.erl",
"afile_server.erl","CVS","afile_server.run",
"ex0204_server.beam"]}}
5> Server ! {self(), {get_file, "hello.erl"}}.
{<0.57.0>,{get_file,"hello.erl"}}
6> receive B -> B end.
{<0.64.0>,
{ok,<<"-module(hello).\n-export([start/0]).\n\nstart() ->\n
io:format(\"hello, world!~n\").\n">>}}
7> Server ! {self(), {put_file, "foo.txt", "foo on you!\n"}}.
{<0.57.0>,{put_file,"foo.txt","foo on you!\n"}}
8> receive C -> C end.
{<0.64.0>,ok}
9> halt().

2021-12-31 08:40:44 dpchrist@tinkywinky ~/sandbox/erlang
$ cat foo.txt
foo on you!


Now that we have figured out the bug -- the shell sent a command to the
server for which there was no matching receive pattern -- it begs the
question: how do I detect when a process is sent a message for which
there is no matching receive pattern?


Here is a naive hack:

2021-12-31 09:31:36 dpchrist@tinkywinky ~/sandbox/erlang
$ cat ex0204_server.erl
-module(ex0204_server).
-export([start/1, loop/1]).

start(Dir) -> spawn(ex0204_server, loop, [Dir]).

loop(Dir) ->
receive
{Client, list_dir} ->
Client ! {self(), file:list_dir(Dir)};
{Client, {get_file, File}} ->
Full = filename:join(Dir, File),
Client ! {self(), file:read_file(Full)};
{Client, {put_file, File, Bytes}} ->
Full = filename:join(Dir, File),
Client ! {self(), file:write_file(Full, Bytes)};
{Client, U} ->
Client ! {self(), {error, "Unknown command", U}};
{Client, U, A} ->
Client ! {self(), {error, "Unknown command", U, A}};
{Client, U, A, B} ->
Client ! {self(), {error, "Unknown command", U, A, B}}
end,
loop(Dir).

2021-12-31 09:31:38 dpchrist@tinkywinky ~/sandbox/erlang
$ erl
Erlang/OTP 19 [erts-8.2.1] [source] [64-bit] [smp:8:8]
[async-threads:10] [kernel-poll:false]

Eshell V8.2.1 (abort with ^G)
1> c(ex0204_server).
{ok,ex0204_server}
2> Server = ex0204_server:start(".").
<0.64.0>
3> Server ! {self(), foo}
3> .
{<0.57.0>,foo}
4> receive A -> A end.
{<0.64.0>,{error,"Unknown command",foo}}
5> Server ! {self(), foo, bar}.
{<0.57.0>,foo,bar}
6> receive B -> B end.
{<0.64.0>,{error,"Unknown command",foo,bar}}
7> Server ! {self(), foo, bar, baz}.
{<0.57.0>,foo,bar,baz}
8> receive C -> C end.
{<0.64.0>,{error,"Unknown command",foo,bar,baz}}
9> halt().


(I presume Chapters 3+ will provide better answers.)


Thank you,

David


p.s. I do not see usage guidelines for erlang-questions on:

https://erlang.org/mailman/listinfo/erlang-questions

Please let me know if there are usage guidelines, FAQ, etc., for
erlang-questions.


p.p.s. When using a mailing list:

1. I "Reply to List", and do not CC the previous author (I presume they
are subscribed).

2. I use (trimmed) bottom/ inline posting style.

Please let me know if erlang-questions uses other conventions.

Yao Bao

unread,
Dec 31, 2021, 9:54:59 PM12/31/21
to erlang-questions@erlang.org Questions
Hello,

> Now that we have figured out the bug -- the shell sent a command to the server for which there was no matching receive pattern -- it begs the question: how do I detect when a process is sent a message for which there is no matching receive pattern?

Perhaps we cannot put any guarantee on this.

> receive
> {Client, list_dir} ->
> Client ! {self(), file:list_dir(Dir)};
> {Client, {get_file, File}} ->
> Full = filename:join(Dir, File),
> Client ! {self(), file:read_file(Full)};
> {Client, {put_file, File, Bytes}} ->
> Full = filename:join(Dir, File),
> Client ! {self(), file:write_file(Full, Bytes)};
> {Client, U} ->
> Client ! {self(), {error, "Unknown command", U}};
> {Client, U, A} ->
> Client ! {self(), {error, "Unknown command", U, A}};
> {Client, U, A, B} ->
> Client ! {self(), {error, "Unknown command", U, A, B}}
> end,

We cannot enumerate all kinds of patterns in the limited receive expression,
but we can solve this problem via properly designed message pattern.

For the receiver, we can say, it can receive one kinds of message,
which is: {Client, Command}, then the sender should send messages matching
this pattern, or, the server has no responsibility for replying anything.

With this design, the receive expression can be written as:

> receive
> {Client, list_dir} ->
> Client ! {self(), file:list_dir(Dir)};
> {Client, {get_file, File}} ->
> Full = filename:join(Dir, File),
> Client ! {self(), file:read_file(Full)};
> {Client, {put_file, File, Bytes}} ->
> Full = filename:join(Dir, File),
> Client ! {self(), file:write_file(Full, Bytes)};
> {Client, Command} ->
> Client ! {self(), {error, "Unknown command", Command}}
> end,

The sender can send such messages:

> Server ! {self(), foo}

> Server ! {self(), {foo, bar}}.
> Server ! {self(), {foo, bar, baz}}.

After sending, the sender say:

receive C -> C end

And pattern C can match those messages happily.

By the way, in my understanding, in the worst case, message passing
is reliable but no guarantee, so the receiver might consider that worst case and
handle it properly (add timeout mechanism is an example).

Cheers,
Yao

Nalin Ranjan

unread,
Jan 1, 2022, 12:37:05 AM1/1/22
to Yao Bao, erlang-questions@erlang.org Questions
How about using an underscore(_) inside the receive ??

नमस्ते।
नलिन रंजन

Yao Bao

unread,
Jan 1, 2022, 2:56:47 AM1/1/22
to Nalin Ranjan, erlang-questions@erlang.org Questions
Underscore (_) or variable (Any) works, but in this case the receiver needs to know the sender’s address to send a reply.

We have to include the address of the sender in the message or register it with a name.

Cheers,
Yao

在 2022年1月1日,13:36,Nalin Ranjan <ranja...@gmail.com> 写道:



Fred Youhanaie

unread,
Jan 1, 2022, 7:45:32 AM1/1/22
to erlang-q...@erlang.org
Hi David

I'll just add a few pointers to what has already been said.

Since you're reusing previous code, you may find the ?MODULE macro handy, e.g. spawn(?MODULE, loop, [Dir]), which should save you from repeating the module name inside the code.

Within the shell, you can use "flush()." to receive and print all the messages in the shell's message queue, if any. Using the "receive X -> X end." pattern will block the shell if the message queue
is empty, as you have already experienced.

For unknown messages, it is probably not a good idea to attempt to reply to unexpected, perhaps even malformed, messages. The simplest pattern to use in the server, while you're learning, would be
something like:

receive
...
Any_msg -> %% make this the last clause in the receive block
io:format("Unknown message: ~p~n", [Any_msg])
end

I think the above do appear at various places in Joe's book.

Enjoy the rest of the book :-)

Cheers
Fred

David Christensen

unread,
Jan 1, 2022, 2:15:17 PM1/1/22
to erlang-q...@erlang.org
On 1/1/22 4:45 AM, Fred Youhanaie wrote:
> Hi David
>
> I'll just add a few pointers to what has already been said.
>
> Since you're reusing previous code, you may find the ?MODULE macro
> handy, e.g. spawn(?MODULE, loop, [Dir]), which should save you from
> repeating the module name inside the code.

> Within the shell, you can use "flush()." to receive and print all the
> messages in the shell's message queue, if any.


Okay.


(To be pedantic: when working exercises in a textbook, I try to use only
the information that has been presented up to that point in the textbook.)


> For unknown messages, it is probably not a good idea to attempt to reply
> to unexpected, perhaps even malformed, messages. The simplest pattern to
> use in the server, while you're learning, would be something like:
>
> receive
>   ...
>   Any_msg -> %% make this the last clause in the receive block
>     io:format("Unknown message: ~p~n", [Any_msg])
> end


I thought of using a catch-all receive pattern and printing an error
message as a default (last) case in the server, but did not know how to
write the code.


That said, it is a better design; so, I will bend my rules and use it
without understanding:

2022-01-01 10:48:28 dpchrist@tinkywinky ~/sandbox/erlang
$ cat ex0204_server.erl
-module(ex0204_server).
-export([start/1, loop/1]).

start(Dir) -> spawn(ex0204_server, loop, [Dir]).

loop(Dir) ->
receive
{Client, list_dir} ->
Client ! {self(), file:list_dir(Dir)};
{Client, {get_file, File}} ->
Full = filename:join(Dir, File),
Client ! {self(), file:read_file(Full)};
{Client, {put_file, File, Bytes}} ->
Full = filename:join(Dir, File),
Client ! {self(), file:write_file(Full, Bytes)};
Any_msg ->
io:format("Unknown message: ~p~n", [Any_msg])
end,
loop(Dir).

2022-01-01 10:51:42 dpchrist@tinkywinky ~/sandbox/erlang
$ erl
Erlang/OTP 19 [erts-8.2.1] [source] [64-bit] [smp:8:8]
[async-threads:10] [kernel-poll:false]

Eshell V8.2.1 (abort with ^G)
1> c(ex0204_server).
{ok,ex0204_server}
2> Server = ex0204_server:start(".").
<0.64.0>
3> Server ! {self(), list_dir}.
{<0.57.0>,list_dir}
4> receive A -> A end.
{<0.64.0>,
{ok,["Makefile",".ex0204_server.erl.swp","afile_client.erl",
"ex0204_server.erl","ex0204_client.erl","hello.erl",
"afile_server.erl","CVS","afile_server.run",
"ex0204_server.beam"]}}
5> Server ! {self(), {get_file, "hello.erl"}}.
{<0.57.0>,{get_file,"hello.erl"}}
6> receive B -> B end.
{<0.64.0>,
{ok,<<"-module(hello).\n-export([start/0]).\n\nstart() ->\n
io:format(\"hello, world!~n\").\n">>}}
7> Server ! {self(), {put_file, "foo", "bar\n"}}.
{<0.57.0>,{put_file,"foo","bar\n"}}
8> receive C -> C end.
{<0.64.0>,ok}
9> Server ! bad_message.
Unknown message: bad_message
bad_message
10> q().
ok
11>
2022-01-01 10:53:50 dpchrist@tinkywinky ~/sandbox/erlang
$ cat foo
bar


Thank you.


David
Reply all
Reply to author
Forward
0 new messages