Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Here is the code: https://github.com/EarlGray/haskell-snippets/blob/master/ext2hs/Ext2.hs

My problem is that it is quite painful to make large Haskell "records" and deserialize a binary layout with Data.Binary.Get manually like this:

instance Binary Superblock where
  get = binGetSuperblock

binGetSuperblock = do
  uint13  <- replicateM 13   getWord32le
  ushort6 <- replicateM 6    getWord16le
  uint4   <- replicateM 4    getWord32le
  ushort2 <- replicateM 2    getWord16le
  return Superblock {
    sInodesCount = uint13 !! 0,           sBlocksCount = uint13 !! 1,
    sReservedBlocksCount = uint13 !! 2,   sFreeBlocksCount = uint13 !! 3,
    sFreeInodesCount = uint13 !! 4,       sFirstDataBlock = uint13 !! 5,
    sLogBlockSize = uint13 !! 6,          sLogClusterSize = uint13 !! 7,
    sBlocksPerGroup = uint13 !! 8,        sClustersPerGroup = uint13 !! 9,
    sInodesPerGroup = uint13 !! 10,       sMountTime = uint13 !! 11,
    sWriteTime = uint13 !! 12,

    sMountsCount = ushort6 !! 0,    sMaxMountsCount = int (ushort6 !! 1),
    sMagic = ushort6 !! 2,          sState = ushort6 !! 3,
    sErrors = ushort6 !! 4,
    sLastCheckTime = uint4 !! 0,    sCheckInterval = uint4 !! 1,
    sCreatorOS = uint4 !! 2,        sRevLevel = (uint4 !! 3, ushort6 !! 5),

    sDefaultResUID = ushort2 !! 0,  sDefaultResGID = ushort2 !! 1,

    sDynRev = Nothing,
    sPrealloc = Nothing,
    sJournaling = Nothing
  }

(this is ext2fs superblock structure).

First of all, I would like to make binary layout as much declarative as possible, abstracting away those ugly sequences of getWord32le in Get monad with temporary fields. I want something like my python code, where binary fields are unpacked to a map according to a struct format string

The second thing I need is a more concise and more clear way for initializing a large Haskell record (creating a lot of temporary named values in the monad is pain).

One way I think of is to read binary fields into a heterogeneous map forall a. BinaryField a => Map String a, accessing each value by its name and pre-extracting most used ones to Haskell. Perhaps Template Haskell and code generation from a declarative format may help, but I have no experience with TH yet.

I would be glad to hear any suggestions or hints about a more idiomatic way (are there any relevant tools?) or about the code in general (though it's just my learning code and it's very incomplete at the moment).

share|improve this question
add comment

2 Answers

up vote 1 down vote accepted

A way to avoid those temporary values and (!!) operators, at the cost perhaps of some obscurity, would be something like the following:

import Control.Applicative ((<$>),(<*>),pure)

binGetSuperblock' = do 
    partiallyAppliedConstructor <- Superblock 
        <$> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord32le
        <*> getWord16le
        <*> (int <$> getWord16le)
        <*> getWord16le
        <*> getWord16le
    outOfPlace16 <- getWord16le
    partiallyAppliedConstructor 
        <$> getWord16le
        <*> getWord32le
        <*> getWord32le
        <*> (OSEnum <$> getWord32le)
        <*> ((,) <$> getWord32le <*> pure outOfPlace16)
        <*> getWord16le
        <*> getWord16le
        <*> pure Nothing
        <*> pure Nothing
        <*> pure Nothing
share|improve this answer
    
Looks good. Do you have any suggestions how to fold this with a heterogeneous list of operations (assuming that there are no out-of-place fields)? –  EarlGray Jan 4 '13 at 0:35
    
But now I see that the ordinary foldl won't work here: its type is (a -> b -> a) -> a -> [b] -> a; since application of <*> changes the type of partially constructed record, (a0 -> b -> a1) must be used. –  EarlGray Jan 4 '13 at 0:51
    
@EarlGray: I take you mean to reduce repetition in those "getWord32le" by supplying a list of them and "filling them" in the record, in order. I suspect you would need Template Haskell for that. –  Daniel Díaz Carrete Jan 4 '13 at 7:35
add comment

To add one more suggestion - building records can be simplified quite a bit by using the RecordWildCards extension, which automatically populates fields from visible names (see GHC docs). Personally, I consider it borderline essential for dealing with large records. I would probably write this like follows:

{-# LANGUAGE RecordWildCards #-}
-- ...
binGetSuperblock = do
  [sInodesCount,         sBlocksCount,
   sReservedBlocksCount, sFreeBlocksCount,
   sFreeInodesCount,     sFirstDataBlock,
   sLogBlockSize,        sLogClusterSize,
   sBlocksPerGroup,      sClustersPerGroup,
   sInodesPerGroup,      sMountTime,
   sWriteTime]                            <- replicateM 13 getWord32le
  [sMountsCount,         sMaxMountsCount,
   sMagic,               sState,
   sErrors,              sRevLevel2]      <- replicateM 6  getWord16le
  [sLastCheckTime,       sCheckInterval,
   sCreatorOS,           sRevLevel1]      <- replicateM 4  getWord32le
  [sDefaultResUID,       sDefaultResGID]  <- replicateM 2  getWord16le
  return Superblock {
    sMaxMountsCount = int sMaxMountsCount,
    sRevLevel = (sRevLevel1, sRevLevel2),
    sDynRev = Nothing,
    sPrealloc = Nothing,
    sJournaling = Nothing,
    ..
    }
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.