distributed-process-0.2.3.0: src/Control/Distributed/Process/Closure.hs
-- | Static values and Closures
--
-- [Static values]
--
-- /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. There is no support for 'static' in ghc yet, however,
-- so we emulate it using Template Haskell. Given a top-level 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.
--
-- [Dealing with type class qualifiers]
--
-- Although 'mkStatic' supports polymorphic types, it does not support
-- qualified types. For instance, you cannot call 'mkStatic' on
--
-- > decode :: Serializable a => ByteString -> a
--
-- Instead, you will need to reify the type class dictionary. Cloud Haskell
-- comes with a reified version of 'Serializable':
--
-- > data SerializableDict a where
-- > SerializableDict :: Serializable a => SerializableDict a
--
-- Using the reified dictionary you can define
--
-- > decodeDict :: SerializableDict a -> ByteString -> a
-- > decodeDict SerializableDict = decode
--
-- where 'decodeDict' is a normal (unqualified) polymorphic value and hence
-- can be passed as an argument to remotable:
--
-- > $(mkStatic 'decodeDict) :: Typeable a => Static (SerializableDict a -> ByteString -> a)
--
-- [Composing static values]
--
-- The version of 'static' provided by this implementation of Cloud Haskell is
-- strictly more expressive than the one proposed in the paper, and additionally
-- supports
--
-- > staticApply :: Static (a -> b) -> Static a -> Static b
--
-- This is extremely useful. For example, Cloud Haskell comes with
-- 'staticDecode' defined as
--
-- > staticDecode :: Typeable a => Static (SerializableDict a) -> Static (ByteString -> a)
-- > staticDecode dict = $(mkStatic 'decodeDict) `staticApply` dict
--
-- 'staticDecode' is used when defining closures (see below), and makes
-- essential use of 'staticApply'.
--
-- Support for 'staticApply' also makes it possible to define a rich set of
-- combinators on 'static' values, a number of which are provided in this
-- module.
--
-- [Static serialization dictionaries]
--
-- Many Cloud Haskell primitives (like 'staticDecode', above) require static
-- serialization dictionaries. In principle these dictionaries require nothing
-- special; for instance, 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 (*).
--
-- [Creating closures manually]
--
-- You don't /need/ to use 'mkClosure', however. Closures are defined exactly
-- as described in /Towards Haskell in the Cloud/:
--
-- > data Closure a = Closure (Static (ByteString -> a)) ByteString
--
-- The splice @$(mkClosure 'isPrime)@ above expands to (prettified a bit):
--
-- > let decoder :: Static (ByteString -> Process Bool)
-- > decoder = $(mkStatic 'isPrime)
-- > `staticCompose`
-- > staticDecode $(functionSDict 'isPrime)
-- > in Closure decoder (encode n)
--
-- where 'staticCompose' is composition of static functions. Note that
-- 'mkClosure' makes use of the static serialization dictionary
-- ('functionSDict') created by 'remotable'.
--
-- [Combinators on Closures]
--
-- Support for 'staticApply' (described above) also means that we can define
-- combinators on Closures, and we provide a number of them in this module,
-- the most important of which is 'cpBind'. Have a look at the implementation
-- of 'Control.Distributed.Process.call' for an example use.
--
-- [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 'staticDecode' is passed an explicit serialization
-- dictionary, we still need the 'Typeable' 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.
module Control.Distributed.Process.Closure
( -- * Creating static values
remotable
, mkStatic
-- * Template-Haskell support for creating closures
, mkClosure
, functionSDict
, functionTDict
-- * Primitive operations on static values
, staticApply
, staticDuplicate
-- * Static functionals
, staticConst
, staticFlip
, staticFst
, staticSnd
, staticCompose
, staticFirst
, staticSecond
, staticSplit
-- * Static constants
, staticUnit
-- * Creating closures
, staticDecode
, staticClosure
, toClosure
-- * Serialization dictionaries (and their static versions)
, SerializableDict(..)
, sdictUnit
, sdictProcessId
, sdictSendPort
-- * Definition of CP and the generalized arrow combinators
, CP
, cpIntro
, cpElim
, cpId
, cpComp
, cpFirst
, cpSecond
, cpSplit
, cpCancelL
, cpCancelR
-- * Closure versions of CH primitives
, cpLink
, cpUnlink
, cpSend
, cpExpect
, cpNewChan
-- * @Closure (Process a)@ as a not-quite-monad
, cpReturn
, cpBind
, cpSeq
) where
import Control.Distributed.Process.Internal.Types
( SerializableDict(..)
, staticApply
, staticDuplicate
)
import Control.Distributed.Process.Internal.Closure.TH
( remotable
, mkStatic
, functionSDict
, functionTDict
)
import Control.Distributed.Process.Internal.Closure.Static
( -- Static functionals
staticConst
, staticFlip
, staticFst
, staticSnd
, staticCompose
, staticFirst
, staticSecond
, staticSplit
-- Static constants
, staticUnit
-- Creating closures
, staticDecode
, staticClosure
, toClosure
-- Serialization dictionaries (and their static versions)
, sdictUnit
, sdictProcessId
, sdictSendPort
)
import Control.Distributed.Process.Internal.Closure.MkClosure (mkClosure)
import Control.Distributed.Process.Internal.Closure.CP
( -- Definition of CP and the generalized arrow combinators
CP
, cpIntro
, cpElim
, cpId
, cpComp
, cpFirst
, cpSecond
, cpSplit
, cpCancelL
, cpCancelR
-- Closure versions of CH primitives
, cpLink
, cpUnlink
, cpSend
, cpExpect
, cpNewChan
-- @Closure (Process a)@ as a not-quite-monad
, cpReturn
, cpBind
, cpSeq
)