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.