evan_tech

Previous Entry Share Next Entry
DBus involves remote method calls, which always means you have to figure out a way to serialize an in-program data structure ("I have a hash of strings to strings") to DBus data structures ("A DBusMessage, where the arguments field contains one DBusArgument which is a DBusArray of pairs of DBusStrings ...") when making calls.

Earlier I had done the (to me) natural thing of making a set of data structures that parallel the DBus ones (like the DBusArgument above*) and then made my "send the message" function take an array of DBusArguments. This is, for example, what the Python bindings do (see dbus.Array).

I had thought I could do something fancier though, 'cause this involves types and if nothing else Haskell is fancy about types. After a few days of pondering I have a much cooler interface now. Three observations, in increasing difficulty level:
  1. When you make a remote call, you now pass ordinary Haskell data as arguments.

    For example, with:
    addArgs msg (34, ["hello", "there"])
    HDBus just uses the type of the arguments to determine how to serialize the data. Even cooler, the compiler will actually complain about the above code for parts that are underspecified -- for example, it doesn't know whether to serialize that "34" as 32-bit int or 16-bit int. So you annotate types, but only where there's ambiguity:
    addArgs msg (34 :: Word32, ["hello", "there"])
    (Word32 is the Haskell type for an unsigned 32-bit integer.) In normal code, you'd probably be using real variables instead of constants like "34" so it's more likely that the types are already known.

  2. A similar but even cooler thing happens on the receiving end, where the "get response arguments" function spits out different things depending on the type you try to get out of it.

    That is, in code like
    usernames <- getResponseArgs msg
    -- "head" gets the first element from a list
    putStrLn (head usernames)
    Since putStrLn wants a string as its argument, the (compile-time) type inference figures out that usernames must be a list of strings, and therefore getResponseArgs calls into the appropriate DBus APIs to deserialize an array of strings.

    This is another one of those creepy-but-awesome instances in Haskell where the way you use a value "later" in the code actually determines what behavior you get out of a function.

  3. Type hackery (and this is maybe just for gaal, I guess...).

    One of the tricky corners in this is the type of an empty array. When I call a function and pass in an empty array, Haskell helpfully generalizes that as type [a], an array of any type. But DBus wants you to serialize an empty array as "here is an zero-element array of strings". It took me a while to realize how and where this problem crops up, but you see it in the Python API docs too:
    For example if a method is expecting an Array of int32's and you need to pass it an empty Array you would do it as such:

    emptyint32array = dbus.Array([], type=dbus.Int32)
    You can fix the type by an explicit annotation, but you still run into problems when you try to serialize the array, as I'll now show.

    Imagine we're deep in the implementation code and want to stick an empty array into the message, and also imagine that the DBus call sequence will be something like:
    subiter = dbus_append_array_start(iter, signature)
    foreach (item in array) { dbus_append_value(subiter, item) }
    dbus_append_array_end(iter)
    The problem is that DBus wants that "signature" parameter, which is a string indicating the type of the contents of the array (for example, "i" for ints, or "as" arrays of strings).

    In Haskell your code cannot talk about types. They're at a level above the code itself, so there's no normal way to ask "what is the type of the stuff in this array?"

    However, if we have a specific value, it's easy enough to write a signature function that uses it. (Here, "Arg" is the class of types that work as DBus parameters.)
    instance Arg Int where
      signature x = "i"   -- signature of any integer "x" is just "i"
    instance Arg String where
      signature x = "s"
    Now signature "foo" gives us the "s" we need for the signature. And if we know there's a value in the array, we can use that in a textbook example of recursion (you can use a list of a list of a list of strings and it'll come out as "aaas"):
    -- the below says: for any Arg 'a', a list of 'a's is also an Arg
    instance Arg a => Arg [a] where
      signature l = "a" ++ signature (head l)
    But when that array is empty, that call to head fails. You don't have a value to pass to such a signature function!

    Brief aside: in an OO language you often have a class like Object that is a supertype of every other type. If you consider subtyping like a DAG instead of a tree (think of multiple inheritance in C++), you could imagine a type at the bottom that is a subtype of every type. (See the picture on the lattice theory article and imagine it's an inheritance diagram.)

    The Haskell spec declares (for complicated reasons I don't understand well enough to explain in blog format) that all errors are the magic value _|_ (aka "bottom"), which causes your program to halt if it's evaluated. Bottom is a super-special value, because it exists in all types -- no matter the context, it's possible to evaluate to an error. (I believe this is tied up with (non)termination because it means that all expressions, including nonterminating ones, have a value.)

    But a magic value that exists for all types sounds just like what we needed. Since the function signature doesn't actually use its argument, the argument never needs to be evaluated, so we can pass in bottom:
    instance Arg a => Arg [a] where
      signature l = "a" ++ signature (_|_ :: a)
    And it works. Yikes.


* This is not how DBus actually works, but the details are not important and this makes the explanation simpler.