-
Notifications
You must be signed in to change notification settings - Fork 0
API proposal
One potential stumbling block of adapting HPX to Haskell is the presence of variadic functions. For example, registering an action requires use of hpx_register_action
:
int hpx_register_action(hpx_action_type_t type, uint32_t attr, const char *key,
hpx_action_t *id, hpx_action_handler_t f,
unsigned int nargs, ...);
And invoking an registered action requires use of _hpx_run
:
int _hpx_run(hpx_action_t *act, int n, ...);
_hpx_run
is a little awkward to translate directly to Haskell, since it wasn't designed to support variable numbers of arguments (with different types, no less!) easily. There are a couple of possible ways to approach this:
The Text.Printf
module in base
cleverly handles variadics by making the type signature of printf
exploit typeclass overloading:
printf :: PrintfType r => String -> r
class PrintfType a
instance IsChar c => PrintfType [c]
instance PrintfType (IO ())
instance (PrintfArg a, PrintfType r) => PrintfType (a -> r)
class PrintfArg a
instance PrintfArg Char
instance PrintfArg Double
instance PrintfArg Float
instance PrintfArg Int
instance PrintfArg Int8
instance PrintfArg Int16
instance PrintfArg Int32
instance PrintfArg Int64
instance PrintfArg Integer
instance PrintfArg Word
instance PrintfArg Word8
instance PrintfArg Word16
instance PrintfArg Word32
instance PrintfArg Word64
instance PrintfArg Natural
instance IsChar c => PrintfArg [c]
This is sufficient to (1) allow printf
to take any number of arguments, (2) allow allow argument types that are instances of PrintfArg
, and (3) produce fairly comprehensible error messages upon failure.
There is a big drawback to this approach in that you cannot control the number of arguments, nor which types are provided. For example, both of these Haskell expressions
printf "%c" "hello"
printf "1" "hello"
are well-types, but result in the respective runtime errors:
λ> printf "%c" "hello"
*** Exception: printf: bad formatting char 'c'
λ> printf "1" "hello"
1*** Exception: printf: formatting string ended prematurely
With printf
, at least you can usually look at the format string as a reference. With HPX, however, the callback and the invocation may be in distinct locations, which would make it harder to get the types right.
A different strategy (which requires a more recent GHC version) would be exploiting type families to statically guarantee that the right number of arguments are passed in, all of the right type.
Let's suppose that the (psuedo-)Haskell binding to _hpx_run
has a type signature resembling this:
hpxRun :: FunPtr x -> a1 -> a2 -> ... -> an -> IO ()
The FunPtr x
argument will be provided as a result of running the appropriate FFI wrapper function (this could probably be generated more easily with Template Haskell or a CPP macro), so in reality, its type will probably be closer to FunPtr (Int -> Char -> IO ())
, modulo some C <-> Haskell type conversion. This is useful to us, because this tells us precisely how many arguments (and of which types) we need. We can use a type family to construct a type-level list containing them:
type family ArgList (a :: *) :: [*] where
ArgList (a -> b) = a ': ArgList b
ArgList a = '[]
We can use ArgList
in conjunction with a heterogeneous list type:
data HList (ts :: [*]) where
HNil :: HList '[]
(:&:) :: t -> HList ts -> HList (t ': ts)
hpxRun :: FunPtr x -> HList (ArgList x) -> IO ()
Now, if you had fp :: FunPtr (Int -> Char -> IO ())
and wanted to invoke it, you'd call
hpxRun fp $ 42 :&: 'z' :&: HNil
If you accidentally passed in an argument of the wrong type, you'd get a compile-time error:
hpxRun fp $ 42 :&: "zanzibar" :&: HNil
Couldn't match type ‘[Char]’ with ‘Char’
Expected type: FunPtr (Int -> Char -> IO ())
-> HList '[Int, [Char]] -> ()
Actual type: FunPtr (Int -> Char -> IO ())
-> HList (ArgList (Int -> Char -> IO ())) -> ()
It works. The downside is that it's a bit more verbose—you'd have to type 42 :&: 'z' :&: HNil
(or 42 :&: singleton 'z'
) instead of simply 42 'z'
. The error message is also slightly more intimidating, but this may be a matter of personal taste.