Understanding this code requires two skills:
- distinguishing between 'definition', which may be infinite (like the set of natural numbers:
naturals = (1 : map '\n->n+1' naturals
), or the list of processed requests) and 'reduction', which is the process of mapping actual data to these definitions
- seeing the structure of this client-server application: it's just a pair of processes talking to eachother: 'client-server' is a bad name, really: it should have been called 'wallace-gromit' or 'foo-bar', or talking philosophers or whatever, but it's symmetrical: the two parties are peers.
As Jon already stated, reduction works in a lazy way (aka 'call by need'): take 2 naturals
would not first evaluate the complete set of naturals, but just take the first one, and prepend that to take 1 (map '\n->n+1' naturals)
, which would reduce to [1,(1+1) ] = [1,2].
Now the structure of the client server app is this (to my eye):
server
is a way to create a list of responses out of a list of requests by using the process
function
client
is a way to create a request based on a response, and append the response of that request to the list of responses.
If you look closely, you see that both are 'a way to create x:xs out of y:ys'. So we could evenly call them wallace
and gromit
.
Now it would be easy to understand if client
would be called with just a list of responses:
someresponses = wallace 0 [1,8,9] -- would reduce to 0,1,8,9
tworesponses = take 2 someresponses -- [0,1]
If the responses are not literally known, but produced by gromit
, we can say
gromitsfirstgrunt = 0
otherresponses = wallace gromitsfirstgrunt (gromit otherresponses)
twootherresponses = take 2 otherresponses -- reduces to [0, take 1 (wallace (gromit ( (next 0):...) )]
-- reduces to [0, take 1 (wallace (gromit ( 0:... ) ) ) ]
-- reduces to [0, take 1 (wallace (1: gromit (...) ) ) ]
-- reduces to [0, take 1 (1 : wallace (gromit (...) ) ) ]
-- reduces to [0, 1 ]
One of both peers needs to 'start' the discussion, hence the initial value provided to wallace
.
Also note the ~ before the pattern of gromit
: this tells Haskell that the contents of the list argument don't need to be reduced - if it sees it's a list, that's enough. There's a nice topic on that in a wikibook on Haskell (look for "Lazy Pattern Matching).