| Safe Haskell | None |
|---|
Control.Distributed.Process.Closure
Contents
Description
Towards Haskell in the Cloud (Epstein et al., Haskell Symposium 2011) proposes a new type construct called static that characterizes values that are known statically. Cloud Haskell uses the Static implementation from Control.Distributed.Static. That module comes with its own extensive documentation, which you should read if you want to know the details. Here we explain the Template Haskell support only.
- Static values
Given a top-level (possibly polymorphic, but unqualified) definition
f :: forall a1 .. an. T f = ...
you can use a Template Haskell splice to create a static version of f:
$(mkStatic 'f) :: forall a1 .. an. (Typeable a1, .., Typeable an) => Static T
Every module that you write that contains calls to mkStatic needs to have a call to remotable:
remotable [ 'f, 'g, ... ]
where you must pass every function (or other value) that you pass as an argument to mkStatic. The call to remotable will create a definition
__remoteTable :: RemoteTable -> RemoteTable
which can be used to construct the RemoteTable used to initialize Cloud Haskell. You should have (at most) one call to remotable per module, and compose all created functions when initializing Cloud Haskell:
let rtable :: RemoteTable rtable = M1.__remoteTable . M2.__remoteTable . ... . Mn.__remoteTable $ initRemoteTable
NOTE: If you get a type error from ghc along these lines
The exact Name `a_a30k' is not in scope Probable cause: you used a unique name (NameU) in Template Haskell but did not bind it
then you need to enable the ScopedTypeVariables language extension.
- Static serialization dictionaries
Some Cloud Haskell primitives require static serialization dictionaries (**):
call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a
Given some serializable type T you can define
sdictT :: SerializableDict T sdictT = SerializableDict
and then have
$(mkStatic 'sdictT) :: Static (SerializableDict T)
However, since these dictionaries are so frequently required Cloud Haskell provides special support for them. When you call remotable on a monomorphic function f :: T1 -> T2
remotable ['f]
then a serialization dictionary is automatically created for you, which you can access with
$(functionSDict 'f) :: Static (SerializableDict T1)
In addition, if f :: T1 -> Process T2, then a second dictionary is created
$(functionTDict 'f) :: Static (SerializableDict T2)
- Closures
Suppose you have a process
isPrime :: Integer -> Process Bool
Then
$(mkClosure 'isPrime) :: Integer -> Closure (Process Bool)
which you can then call, for example, to have a remote node check if a number is prime.
In general, if you have a monomorphic function
f :: T1 -> T2
then
$(mkClosure 'f) :: T1 -> Closure T2
provided that T1 is serializable (*) (remember to pass f to remotable).
(You can also create closures manually--see the documentation of Control.Distributed.Static for examples.)
- Example
Here is a small self-contained example that uses closures and serialization dictionaries. It makes use of the Control.Distributed.Process.SimpleLocalnet Cloud Haskell backend.
{-# LANGUAGE TemplateHaskell #-} import System.Environment (getArgs) import Control.Distributed.Process import Control.Distributed.Process.Closure import Control.Distributed.Process.Backend.SimpleLocalnet import Control.Distributed.Process.Node (initRemoteTable) isPrime :: Integer -> Process Bool isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..] where sieve :: [Integer] -> [Integer] sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0] remotable ['isPrime] master :: [NodeId] -> Process () master [] = liftIO $ putStrLn "no slaves" master (slave:_) = do isPrime79 <- call $(functionTDict 'isPrime) slave ($(mkClosure 'isPrime) (79 :: Integer)) liftIO $ print isPrime79 main :: IO () main = do args <- getArgs case args of ["master", host, port] -> do backend <- initializeBackend host port rtable startMaster backend master ["slave", host, port] -> do backend <- initializeBackend host port rtable startSlave backend where rtable :: RemoteTable rtable = __remoteTable initRemoteTable - Notes
(*) If T1 is not serializable you will get a type error in the generated code. Unfortunately, the Template Haskell infrastructure cannot check a priori if T1 is serializable or not due to a bug in the Template Haskell libraries (http://hackage.haskell.org/trac/ghc/ticket/7066)
(**) Even though call is passed an explicit serialization dictionary, we still need the Serializable constraint because Static is not the true static. If it was, we could unstatic the dictionary and pattern match on it to bring the Typeable instance into scope, but unless proper static support is added to ghc we need both the type class argument and the explicit dictionary.
- data SerializableDict a where
- SerializableDict :: Serializable a => SerializableDict a
- staticDecode :: Typeable a => Static (SerializableDict a) -> Static (ByteString -> a)
- sdictUnit :: Static (SerializableDict ())
- sdictProcessId :: Static (SerializableDict ProcessId)
- sdictSendPort :: Typeable a => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
- type CP a b = Closure (a -> Process b)
- idCP :: Typeable a => CP a a
- splitCP :: (Typeable a, Typeable b, Typeable c, Typeable d) => CP a c -> CP b d -> CP (a, b) (c, d)
- returnCP :: forall a. Serializable a => Static (SerializableDict a) -> a -> Closure (Process a)
- bindCP :: forall a b. (Typeable a, Typeable b) => Closure (Process a) -> CP a b -> Closure (Process b)
- seqCP :: (Typeable a, Typeable b) => Closure (Process a) -> Closure (Process b) -> Closure (Process b)
- cpLink :: ProcessId -> Closure (Process ())
- cpUnlink :: ProcessId -> Closure (Process ())
- cpSend :: forall a. Typeable a => Static (SerializableDict a) -> ProcessId -> CP a ()
- cpExpect :: Typeable a => Static (SerializableDict a) -> Closure (Process a)
- cpNewChan :: Typeable a => Static (SerializableDict a) -> Closure (Process (SendPort a, ReceivePort a))
- remotable :: [Name] -> Q [Dec]
- remotableDecl :: [Q [Dec]] -> Q [Dec]
- mkStatic :: Name -> Q Exp
- mkClosure :: Name -> Q Exp
- functionSDict :: Name -> Q Exp
- functionTDict :: Name -> Q Exp
Serialization dictionaries (and their static versions)
data SerializableDict a whereSource
Reification of Serializable (see Control.Distributed.Process.Closure)
Constructors
| SerializableDict :: Serializable a => SerializableDict a |
Instances
staticDecode :: Typeable a => Static (SerializableDict a) -> Static (ByteString -> a)Source
Static decoder, given a static serialization dictionary.
See module documentation of Control.Distributed.Process.Closure for an example.
sdictUnit :: Static (SerializableDict ())Source
Serialization dictionary for '()'
sdictProcessId :: Static (SerializableDict ProcessId)Source
Serialization dictionary for ProcessId
sdictSendPort :: Typeable a => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))Source
Serialization dictionary for SendPort
The CP type and associated combinators
type CP a b = Closure (a -> Process b)Source
CP a b is a process with input of type a and output of type b
splitCP :: (Typeable a, Typeable b, Typeable c, Typeable d) => CP a c -> CP b d -> CP (a, b) (c, d)Source
returnCP :: forall a. Serializable a => Static (SerializableDict a) -> a -> Closure (Process a)Source
bindCP :: forall a b. (Typeable a, Typeable b) => Closure (Process a) -> CP a b -> Closure (Process b)Source
seqCP :: (Typeable a, Typeable b) => Closure (Process a) -> Closure (Process b) -> Closure (Process b)Source
CP versions of Cloud Haskell primitives
cpNewChan :: Typeable a => Static (SerializableDict a) -> Closure (Process (SendPort a, ReceivePort a))Source
Template Haskell support for creating static values and closures
remotable :: [Name] -> Q [Dec]Source
Create the closure, decoder, and metadata definitions for the given list of functions
remotableDecl :: [Q [Dec]] -> Q [Dec]Source
Like remotable, but parameterized by the declaration of a function instead of the function name. So where for remotable you'd do
f :: T1 -> T2 f = ... remotable ['f]
with remotableDecl you would instead do
remotableDecl [ [d| f :: T1 -> T2 ; f = ... |] ]
remotableDecl creates the function specified as well as the various dictionaries and static versions that remotable also creates. remotableDecl is sometimes necessary when you want to refer to, say, $(mkClosure 'f) within the definition of f itself.
NOTE: remotableDecl creates __remoteTableDecl instead of __remoteTable so that you can use both remotable and remotableDecl within the same module.
mkStatic :: Name -> Q ExpSource
Construct a static value.
If f : forall a1 .. an. T then $(mkStatic 'f) :: forall a1 .. an. Static T. Be sure to pass f to remotable.