[erlang-questions] General advice on FP (& Erlang)

igwan <>
Sat Aug 11 22:40:45 CEST 2007


Hello Milen,

Milen Georgiev Dzhumerov a écrit :
> And I definitely have got to ask this - how are we supposed to mutate 
> state? Using processes? Function calls? I just can't wrap my head 
> around how some C code is going to be translated.
In FP, your state is kept in your function arguments. You call your 
function with your initial state in the arguments, you do your stuff and 
pass modified state (arguments) to the same function, and again ... This 
is recursion and this is how you modify your state. When you reach a 
certain state (certain conditions are met on your arguments), you return 
the result.

>
> Let's take a simple example. I'm writing a network app in Erlang which 
> accepts "messages" on a socket, where a "message" is defined as 
> length:content. If I was to do this in C, I would have a function 
> which gets called every time bytes arrive on the socket and then 
> depending on which state I'm in, I'm either going to append the bytes 
> to a length buffer and when I reach the ":" character, I'll change the 
> state and start appending to the content buffer (while decrementing 
> the number of bytes still left to be read). 
I'll give a quick example with comments. Let's suppose the list of 
received bytes is a list. I'm not going to pass the length/content 
states across a big function, but instead decompose them into two functions.

-module(simple).

-export([parse_bytes/1]).

parse_bytes(Received_bytes) ->
    % We set an initial state
    Length_buffer = [],
    parse_length(Received_bytes, Length_buffer).

%% We handle the case where we finished parsing the "length" first
parse_length(":" ++ Rest, Length_buffer) ->
    Length = list_to_integer(lists:reverse(Length_buffer)),
    % we now parse the rest of our message
    parse_content(Rest, Length);

%% We handle one byte of the length field
parse_length([Byte | Rest], Length_buffer) ->
    % Here we accumulate our Byte in the buffer
    New_length_buffer = [Byte|Length_buffer],
    % And we call ourself with the modified state
    parse_length(Rest, New_length_buffer).


parse_content(Bytes, Length) ->
    % We need a list to accumulate the content
    Content_buffer = [],
    parse_content(Bytes, Length, Content_buffer).

%% We first handle the case where length is 0
%% (in the original message, or after we finished parsing the whole 
Content field)
parse_content(Bytes_left, 0, Content_buffer) ->
    % return Content_buffer and remaining bytes (from the next "message")
   {lists:reverse(Content_buffer), Bytes_left};

parse_content([Byte | Rest], N, Content_buffer) ->
    % accumulate the Byte
    New_content_buffer = [Byte|Content_buffer],
    % call ourself with the modified state
    parse_content(Rest, N - 1, New_content_buffer).

And then call my function :

1> simple:parse_bytes("6:erlang2:is3:easy").
{"erlang","2:is3:easy"}
2> {Content, Rest} = simple:parse_bytes("6:erlang2:is3:easy").
{"erlang","2:is3:easy"}
3> Content.
"erlang"
4> Rest.
"2:is3:easy"
5> simple:parse_bytes(Rest).
{"is","3:easy"}

Then you can write a function that makes use of parse_bytes(Bytes) 
calling it recursively and accumulating the result.

As a simple rule of thumb, in a functional computation,  you work only 
with the data you were provided in the arguments or that you obtain by 
calling another function, you then do your transformations, and either 
return, call yourself with a modified "state", or call another function 
(as a tail call).
The control flow in Erlang is achieved with pattern-matching your 
"state" with the different clauses of the function.
Hope this helps.


igwan

   






More information about the erlang-questions mailing list