[Haskell-cafe] A practical Haskell puzzle

Heinrich Apfelmus apfelmus at quantentunnel.de
Mon Feb 28 10:00:03 CET 2011


Yitzchak Gale wrote:
> You have written a large software system in Haskell. Wishing to
> play to Haskell's strength, you have structured your system
> as a series of composable layers. So you have data types
> 
> Layer1, Layer2, ...
> 
> and functions
> 
> layer2 :: Layer1 -> Layer2
> layer3 :: Layer2 -> Layer3
> ....
> 
> etc.
> 
> Of course you'll want to be able to run any range
> of the layers separately, at least for testing and debugging
> purposes if not for the functionality itself.
> 
> So your UI module (command line or whatever) that launches
> your application provides a data type
> 
> data Layers = Layers Int Int
> 
> that indicates which layers to run, and functions
> 
> deserialize1 :: L.ByteString -> Layer1
> deserialize2 :: L.ByteString -> Layer2
> ....
> 
> serialize1 :: Layer1 -> L.ByteString
> serialize2 :: Layer2 -> L.ByteString
> ....
> 
> etc.
> 
> Now you need a function
> 
> runLayers :: Layers -> L.ByteString -> L.ByteString
> 
> so that the effect is for example
> 
> runLayers (Layers 4 6) = serialize6 . layer6 . layer5 . deserialize4
> 
> [..]
> 
> What is the best way to write runLayers? Feel free to change
> the details of the above design, as long as it meets the
> functionality requirements expressed.

Solution: compose all the functions, but do not use the standard 
function composition (.) to do that. Instead, make a new data type with 
composition as constructor. This way, you can inspect the composition 
afterwards and run only parts of it.

Solution, put differently: Make a type-safe list of the whole chain of 
functions. Then, the  runLayers  function throws away everything outside 
the range and composes what is left.

Here a rough sketch of what I have in mind:

    data Compoz a b where
        Id   :: Compoz a a
        Cons :: (Serialize a,b,c) => (b -> c) -> Compoz a b -> Compoz a c

    -- this value needs to be written out
    chain = layer20 `Cons` layer 19 ...

    runLayers (Layer a b) =
        deserialize . (run . takeC (b-a) . dropC a $ chain) . serialize

    takeC :: Int -> Compoz a b -> (exists c. Compoz a c)
    dropC :: Int -> Compoz a b -> (exists c. Compoz c b)

    run :: Compoz a b -> (a -> b)

Of course, you will have to wrestle with the existential types for 
takeC  and  dropC  a bit, but that shouldn't be much of a problem. For 
instance, you can fuse these functions into  runLayers  and hide the 
existential types somewhere in the recursion.


Regards,
Heinrich Apfelmus

--
http://apfelmus.nfshost.com




More information about the Haskell-Cafe mailing list