I've been playing with Cloud Haskell. I've noticed in the hackage documentation there's a kind of applicative interface. But in particular I'm trying to find or write a function closurePure
with the following signature:
closurePure :: (Typeable a, Binary a) => a -> Closure a
This is basically a restricted version of pure.
Whilst the Closure
datatype itself is abstract, the following closure
provided:
closure :: Static (ByteString -> a) -> ByteString -> Closure a
So I can get this far:
closurePure :: (Typeable a, Binary a) => a -> Closure a
closurePure x = closure ??? (encode x)
The problem is what to put where the ???
s are.
My first attempt was the following:
myDecode :: (Typeable a, Binary a) => Static (ByteString -> a)
myDecode = staticPtr (static decode)
But upon reading the GHC docs on static pointers, the show
example suggested to me that you can't have a constraint because a constrained function doesn't have a Typeable
instance. So I tried the work around suggested using Dict
:
myDecode :: Typeable a => Static (Dict (Binary a) -> ByteString -> a)
myDecode = staticPtr (static (\Dict -> decode))
But now I've got the wrong type that doesn't fit into the closure
function above.
Is there anyway to write closurePure
or something similar (or have I missed it in the Cloud Haskell docs)? Raising binary
plain types to Closure
s seems essential to using the applicative interface given, but I can't work out how to do it.
Note that I can do this:
class StaticDecode a where
staticPtrDecode :: StaticPtr (ByteString -> a)
instance StaticDecode Int where
staticPtrDecode = static Data.Binary.decode
instance StaticDecode Float where
staticPtrDecode = static Data.Binary.decode
instance StaticDecode Integer where
staticPtrDecode = static Data.Binary.decode
-- More instances etc...
myPure :: forall a. (Typeable a, StaticDecode a, Binary a) => a -> Closure a
myPure x = closure (staticPtr staticPtrDecode) (encode x)
Which works well but basically requires me to repeat an instance for each Binary
instance. It seems messy and I'd prefer another way.
You're right,
Closure
has an applicative-like structure, a fact made even more explicit in both the interface and the implementation of distributed-closure. It's not quite applicative, because in thepure
case we do have the additional constraint that the argument must somehow be serializable.Actually, we have a stronger constraint. Not only must the argument be serializable, but the constraint must itself be serializable. Just like it's hard to serialize functions directly, you can imagine that it's hard to serialize constraints. But just like for functions, the trick is to serialize a static pointer to the constraint itself, if such a static pointer exists. How do we know that such a pointer exists? We could introduce a type class with a single method that gives us the name of the pointer, given a constraint:
There's a slight technical trick going on here. The kind of the type index for
StaticPtr
is the kind*
, whereas a constraint has kindConstraint
. So we reuse a trick from the constraints library that consists in wrapping a constraint into a data type (Dict
above), which like all data types is of kind*
. Constraints that have an associatedGimmeStaticPtr
instance are called static constraints.In general, it's sometimes useful to compose static constraints to get more static constraints.
StaticPtr
is not composable, butClosure
is. so whatdistributed-closure
actually does is define a similar class, that we'll call,Now we can define
closurePure
in a similar way that you did:It would be great if in the future, the compiler could resolve
GimmeClosure
constraints on-the-fly by generating static pointers as needed. But for now, the thing that comes closest is Template Haskell. distributed-closure provides a module to autogenerateGimmeClosure (Cls a)
constraints at the definition site for classCls
. SeewithStatic
here.Incidentally, Edsko de Vries gave a great talk about distributed-closure and the ideas embodied therein.