My dbus-library has a type for DBus types like this
data DBusSimpleType
= TypeByte
| TypeBoolean
| TypeInt16
| TypeUInt16
[...]
data DBusType
= DBusSimpleType DBusSimpleType -- ^ A simple type
| TypeArray DBusType -- ^ Variable-length homogenous arrays
| TypeStruct [DBusType] -- ^ Structs (Tuples) and a list of member types
[...]
DBus values are encoded as a GADT:
data DBusValue :: DBusType -> * where
DBVByte :: Word8 -> DBusValue ('DBusSimpleType TypeByte)
DBVBool :: Bool -> DBusValue ('DBusSimpleType TypeBoolean)
DBVInt16 :: Int16 -> DBusValue ('DBusSimpleType TypeInt16)
DBVUInt16 :: Word16 -> DBusValue ('DBusSimpleType TypeUInt16)
[...]I also have a Type class for marshalling Haskell types:
class SingI (RepType a) => Representable a where
-- | The 'DBusType' that represents this type
type RepType a :: DBusType
-- | Conversion from Haskell to D-Bus types
toRep :: a -> DBusValue (RepType a)
-- | Conversion from D-Bus to Haskell types.
fromRep :: DBusValue (RepType a) -> Maybe a
with instances for Text, Int8 etc. and template haskell to automatically create representations for complex types.
There is a call function that calls dbus methods and can return values. The question is: Should it
- Return the DBusValue (almost never what you want)
- Marshal to Haskell via fromRep (convenient, but often requires type anotations because of non-injectivity of RepType)
- Marshal to a uniquely selected Haskell type (makes Representable useless but avoids type anotations)
The burden of writing annotations in 2 can be lessened by a newtype that fixed the
type and only needs to be pattern matched on, that is, the following never needs
a type annotation, even when result is used a polymorphic setting:
R result <- call [...]