Monday, September 17, 2012

rpc-framework #2: Sessions as Closures


    I'd like to theorize that RPC can really form more of a communicative solution than older wisdom would claim.  I've seen the following claims in numerous places regarding RPC:
  1. It hides the existence of the network from the user.
  2. It hides the location of data in the network.
  3. It hides message passing communication from the user and prevents control of this process. Specifically, local calls can fail without knowing whether the remote call succeeded or not. 
    These drawbacks may be valid for RMI in Java, but they aren't as much of a problem with the implementation in rpc-framework.
  1. While rpc-framework does hide the calls corresponding to sockets, ports, file-io, data marshaling, and concurrency, it does not make an attempt to hide that each remote call to a remote function is on a network.  The WIO monad signifies that remote calls might be made and includes the world that the action is executing in.
  2. In keeping with the Haskell mentality, values which have effects, such as network calls, have types which recognize these effects.  Data returned from a remote call will have an effect type if using that data will require a remote call.  For example, if "getFoo :: WIO Server IO (Int -> Int)" is called as a remote function, the type of the function it returns will be "Int -> WIO m IO Int"  rather than the opaque "Int -> Int".  Remote calls aren't in any way hidden.
  3. If you do not accept that remote calls send and receive data using "show" and "read", then yes, this communication is hidden.  But I'd like to present some methods for controlling communication using closures, which you can hopefully extend to your own needs. 
    The mentality behind RPC shouldn't be to hide communication, but to remove the boilerplate of marshaling data and connecting message passing to message receiving while improving the type safety of the communication.

Simple Sessions


    Suppose you want to create a Server/Client system where the client connects to the server with credentials then receives authorization to perform certain actions on the server.  For example, the guess the number game from the CMU SkillSwap 2012.  Here we want for every client that connects to the server, to generate a session with a new random number for that client to guess where the client can then query the server to see if his guess is correct. 

Solution Without RPC  

   There are a few ways to accomplish this.  In a flat system without RPC, the obvious way would be to have the server assign the client a unique identifier, which we then pass to the client.  We then save the a new random number to a hash table using the identifier as a key.  We would then create a server which accepts requests given given the identifier and a random number guess and queries the global state hash-table for the information connected to the identifier. 

Solution With RPC

   That solution contains lots of boilerplate.  You'd need to setup a server which responds to two different kinds of requests in parallel, write a session hash-table, pass around identifiers, write parsers and printers for your data, you'd also have to free the data in the hash-table when it is no longer needed. The first step to doing this better is to notice that rpc-framework gives you most of this for free.
   Every time you return a function from a remote call, a new service is set up listening for it's unique identifier.  This identifier is what is actually being passed to the caller.  Furthermore, since the returned closure is attached to the servlet, the data it needs to work is saved, similar to attaching a value to a key in a hash-table.   Thus, closures describe simple sessions in this system.  All this potential boilerplate code can be reduced to the following few lines of code:
    sessionServer = do
      onHost Server
      r <- newRandomInteger
      return $ \i -> compare i r