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 Closures 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,
Closurehas 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 thepurecase 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
StaticPtris 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 (Dictabove), which like all data types is of kind*. Constraints that have an associatedGimmeStaticPtrinstance are called static constraints.In general, it's sometimes useful to compose static constraints to get more static constraints.
StaticPtris not composable, butClosureis. so whatdistributed-closureactually does is define a similar class, that we'll call,Now we can define
closurePurein a similar way that you did:It would be great if in the future, the compiler could resolve
GimmeClosureconstraints 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. SeewithStatichere.Incidentally, Edsko de Vries gave a great talk about distributed-closure and the ideas embodied therein.