[Haskell-cafe] Advice needed on how to improve some code
Jeff
jeff at datalinktech.com.au
Thu Apr 16 00:57:41 UTC 2015
Hello,
I am seeking some advice on how I might improve a bit of code.
The function in question reads and parses part of a binary protocol, storing the parsed info as it proceeds.
parseDeviceData is called by parseDevice (shown further down).
It looks to me like there should be a more concise, less repetitive way to do what
parseDeviceData does. Any advice on this would be greatly appreciated.
parseDeviceData :: P.Payload -> Parser P.Payload
parseDeviceData pl =
let
mdm = P.dataMask ( P.payloadData pl )
in
( let pld = P.payloadData pl in
if testBit mdm ( fromEnum D.Sys )
then
parseDeviceSysData >>=
( \s -> return ( pl { P.payloadData = pld { P.sysData = Just s } } ) )
else
return pl ) >>=
( \pl' -> let pld = P.payloadData pl' in
if testBit mdm ( fromEnum D.GPS )
then
parseDeviceGPSData >>=
( \s -> return ( pl' { P.payloadData = pld { P.gpsData = Just s } } ) )
else
return pl' ) >>=
( \pl' -> let pld = P.payloadData pl' in
if testBit mdm ( fromEnum D.GSM )
then
parseDeviceGSMData >>=
( \s -> return ( pl' { P.payloadData = pld { P.gsmData = Just s } } ) )
else
return pl' ) >>=
( \pl' -> let pld = P.payloadData pl' in
if testBit mdm ( fromEnum D.COT )
then
parseDeviceCOTData >>=
( \s -> return ( pl' { P.payloadData = pld { P.cotData = Just s } } ) )
else
return pl' ) >>=
( \pl' -> let pld = P.payloadData pl' in
if testBit mdm ( fromEnum D.ADC )
then
parseDeviceADCData >>=
( \s -> return ( pl' { P.payloadData = pld { P.adcData = Just s } } ) )
else
return pl' ) >>=
( \pl' -> let pld = P.payloadData pl' in
if testBit mdm ( fromEnum D.DTT )
then
parseDeviceDTTData >>=
( \s -> return ( pl' { P.payloadData = pld { P.dttData = Just s } } ) )
else
return pl' ) >>=
( \pl' -> let pld = P.payloadData pl' in
if testBit mdm ( fromEnum D.OneWire )
then
parseDeviceOneWireData >>=
( \s -> return ( pl' { P.payloadData = pld { P.iwdData = Just s } } ) )
else
return pl' ) >>=
( \pl' -> if testBit mdm ( fromEnum D.ETD )
then
parseDeviceEventData pl'
else
return pl' )
The Parser above is a Data.Binary.Strict.Get wrapped in a StateT, where the state is a top-level
structure for holding the parsed packet.
parseDevice :: Bool -> Parser ()
parseDevice _hasEvent =
parseTimestamp >>=
( \ts ->
if _hasEvent
then
lift getWord8 >>=
( \e -> lift getWord16be >>=
( \mdm ->
return ( P.Payload "" ( Just ts ) $
P.blankDevicePayloadData { P.dataMask = mdm
, P.eventID = toEnum ( fromIntegral e .&. 0x7f )
, P.deviceStatusFlag = testBit e 7
, P.hasEvent = True
} ) ) )
else
lift getWord16be >>=
( \mdm ->
return ( P.Payload "" ( Just ts ) $
P.blankDevicePayloadData { P.dataMask = mdm } ) )
) >>=
parseDeviceData >>=
( \dpl -> get >>= ( \p -> put ( p { P.payloads = dpl : P.payloads p } ) ) )
Here are the data types for the Packet and Payload:
data Payload = Payload { imei :: !BS.ByteString
, timestamp :: Maybe Word64
, payloadData :: PayloadData
}
data PayloadData = HeartBeatPL
| SMSFwdPL { smsMesg :: !BS.ByteString }
| SerialPL { auxData :: !Word8
, fixFlag :: !Word8
, gpsCoord :: !GPSCoord
, serialData :: !BS.ByteString
}
| DevicePL { hasEvent :: !Bool
, deviceStatusFlag :: !Bool
, eventID :: !E.EventID
, dataMask :: !Word16
, sysData :: Maybe DS.SysData
, gpsData :: Maybe DGP.GPSData
, gsmData :: Maybe DGS.GSMData
, cotData :: Maybe DC.COTData
, adcData :: Maybe DA.ADCData
, dttData :: Maybe DD.DTTData
, iwdData :: Maybe DO.OneWireData
, etdSpd :: Maybe ES.SpeedEvent
, etdGeo :: Maybe EG.GeoEvent
, etdHealth :: Maybe EH.HealthEvent
, etdHarsh :: Maybe EHD.HarshEvent
, etdOneWire :: Maybe EO.OneWireEvent
, etdADC :: Maybe EA.ADCEvent
}
deriving ( Show )
data Packet = Packet { protocolVersion :: !Word8
, packetType :: !PT.PacketType
, deviceID :: Maybe BS.ByteString
, payloads :: ![ Payload ]
, crc :: !Word16
}
deriving ( Show )
Lastly, here is the Parser monad transformer:
module G6S.Parser where
import Control.Monad.State.Strict
import Data.Binary.Strict.Get
import qualified Data.ByteString as BS
import qualified G6S.Packet as GP
type Parser = StateT GP.Packet Get
runParser :: Parser a -> BS.ByteString -> Maybe a
runParser p bs =
let
( result, _ ) = runGet ( runStateT p GP.initPacket ) bs
in
case result of
Right tup -> Just $ fst tup
Left _ -> Nothing
I hope there is enough info here.
Thanks,
Jeff
More information about the Haskell-Cafe
mailing list