I’m writing bindings to a C library that interfaces with hardware. One of the functions reads an attribute with a name (the CString
below) from a device (the DeviceProxyPtr
):
tango_read_attribute :: DeviceProxyPtr -> CString -> Ptr HaskellAttributeData -> IO ()
This C function fills HaskellAttributeData
, which is a record. For now, only one of the record fields is important: tangoAttributeData :: HaskellTangoAttributeData
- the actual data. This, in turn, is defined as:
data HaskellTangoAttributeData
= HaskellAttributeDataBoolArray !(HaskellTangoVarArray CBool)
| HaskellAttributeDataCharArray !(HaskellTangoVarArray CChar)
| HaskellAttributeDataShortArray !(HaskellTangoVarArray CShort)
| HaskellAttributeDataUShortArray !(HaskellTangoVarArray CUShort)
| many more types here
Finally, HaskellTangoVarArray
is:
data HaskellTangoVarArray a = HaskellTangoVarArray
{ varArrayLength :: Word32,
varArrayValues :: Ptr a
}
(even for a scalar attribute, we get an array, and then take only the first element - but there are array attributes as well and even 2D images)
Sorry for the long opener, but I didn’t know how to cut it down further.
The problem is, I’d like to expose functions like readBoolAttribute
, readShortAttribute
, readUShortAttribute
and so on, but without repeating myself too much. My current approach is pretty minimal:
readBoolAttribute :: DeviceProxy -> AttributeName -> IO Bool
readBoolAttribute =
readAttributeSimple extract
where
extract (HaskellAttributeDataBoolArray (HaskellTangoVarArray {varArrayValues})) = Just . (/= 0) <$> peek varArrayValues
extract _ = pure Nothing
As you can see, I’ve written a function readAttributeSimple
that takes care of the FFI specific things and leaves the extract
function to pattern-match the right constructor type and then convert to the target format.
My question is, can I write this with even less repetition? Via Template Haskell? Type magic? I’m willing to include extensions from, say, ghc 9.6, if it helps.