packages feed

distributed-closure 0.4.0 → 0.4.1

raw patch · 6 files changed

+191/−45 lines, 6 filesdep +asyncdep ~basedep ~binarynew-component:exe:example-client-serverPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: async

Dependency ranges changed: base, binary

API changes (from Hackage documentation)

- Control.Distributed.Closure: instance (Data.Typeable.Internal.Typeable b, Data.Typeable.Internal.Typeable a) => Data.Binary.Class.Binary (Control.Distributed.Closure.WrappedArrowClosure a b)
+ Control.Distributed.Closure: instance (Control.Distributed.Closure.Static c1, Control.Distributed.Closure.Static c2, Data.Typeable.Internal.Typeable c1, Data.Typeable.Internal.Typeable c2, (c1, c2)) => Control.Distributed.Closure.Static (c1, c2)
+ Control.Distributed.Closure: instance (Data.Typeable.Internal.Typeable a, Data.Typeable.Internal.Typeable b) => Data.Binary.Class.Binary (Control.Distributed.Closure.WrappedArrowClosure a b)
- Control.Distributed.Closure: data Dict a :: Constraint -> *
+ Control.Distributed.Closure: data Dict a

Files

README.md view
@@ -35,6 +35,11 @@   spawn "someAddress" (static hello `cap` name) ``` +An example of sending static pointers and closures+through a communication channel is provided under+[examples/ClientServer.hs](examples/ClientServer.hs)+in the source repository.+ `distributed-closure` does not implement sending/receiving/spawning closures - that's left for higher-level frameworks. Only closure creation, composition and (de)serialization.
distributed-closure.cabal view
@@ -1,5 +1,5 @@ name:                distributed-closure-version:             0.4.0+version:             0.4.1 synopsis:            Serializable closures for distributed programming. description:         See README. homepage:            https://github.com/tweag/distributed-closure@@ -49,3 +49,14 @@                     distributed-closure,                     hspec >= 2.1,                     QuickCheck >= 2.8++executable example-client-server+  main-is:          ClientServer.hs+  hs-source-dirs:   examples+  default-language: Haskell2010+  ghc-options:      -Wall+  build-depends:    base >= 4.8+                  , async >= 2.1+                  , binary >= 0.7+                  , bytestring >= 0.10+                  , distributed-closure
+ examples/ClientServer.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StaticPointers #-}+{-# LANGUAGE TemplateHaskell #-}++import Control.Concurrent.Async+import Control.Concurrent.Chan+import Control.Distributed.Closure+import Control.Distributed.Closure.TH+import Control.Monad (forever)+import Data.Binary+import qualified Data.ByteString.Lazy as BSL+import Data.Functor.Static+import Data.Typeable (Typeable)+import GHC.Generics+import GHC.StaticPtr++-- | An instruction to the server.+data Instruction+  = CallStatic StaticKey Int+    -- ^ @CallStatic skFun arg@+    --+    -- Apply the function behind the 'StaticKey' @skFun@ to @arg@.+  | CallClosure (Closure (Int -> Int)) Int+    -- ^ @CallClosure cl arg@+    --+    -- Apply the closure @cl@ to @arg@.+  deriving Generic+instance Binary Instruction++-- | Handle an instruction by the client.+--+-- This is where we resolve a 'StaticKey'+-- by looking up the 'StaticPtr' and dereferencing it.+--+-- This is also where we resolve a 'Closure'.+handleInstruction :: Instruction -> IO (Maybe Int)+handleInstruction (CallStatic skey input) = do+  mbSPtr <- unsafeLookupStaticPtr skey+  return $ case mbSPtr of+    Nothing -> Nothing+    Just sptr ->+      let fun = deRefStaticPtr sptr in+      Just $ fun input+handleInstruction (CallClosure cl input) =+  let fun = unclosure cl in+  return $ Just $ fun input++-- | Channel to which a client will send its request.+type ServerChan = Chan (BSL.ByteString, ResponseChan)++-- | Channel to which the server will send its response.+type ResponseChan = Chan BSL.ByteString++-- | Execute an action with a concurrent server thread.+--+-- This mocks a network connection between a client and a server process.+-- For simplicity, the client and server run within the same process+-- and communicate through 'Chan's instead of sockets.+--+-- The server listens on a channel for requests.+-- The client sends requests on that channel together with a response channel.+-- The server handles the request and sends the result on the response channel.+withServer :: (ServerChan -> IO ()) -> IO ()+withServer action = do+  serverChan <- newChan+  let server = forever $ do+        (body, responseChan) <- readChan serverChan+        result <- case decodeOrFail body of+          Left _ -> return Nothing+          Right (_, _, instruction) -> handleInstruction instruction+        writeChan responseChan (encode result)+  withAsync server (\_ -> action serverChan)++-- | A global function that can be packed into a 'CallStatic' instruction.+double :: Int -> Int+double = (*2)++-- | A wrapper around 'Int' used to fulfill the 'Serializable' constraint,+-- so that it can be packed into a 'Closure'.+newtype SerializableInt = SI Int deriving (Generic, Typeable)+withStatic [d|+  instance Binary SerializableInt+  instance Typeable SerializableInt+  |]++-- | Demonstration of client server interactions.+main :: IO ()+main = withServer $ \serverChan -> do+  do+    clientChan <- newChan+    -- Obtain the 'StaticPtr' to the global function 'double'+    -- using the 'static' keyword, enabled by the 'StaticPointers' extension.+    -- Convert the 'StaticPtr' into a 'StaticKey' using 'staticKey',+    -- so that it can be sent across the wire.+    let fun = staticKey $ static double+        request = encode $ CallStatic fun 4+    writeChan serverChan (request, clientChan)+    result <- decode <$> readChan clientChan+    putStrLn $ "double 4 = " ++ show (result :: Maybe Int)+  do+    clientChan <- newChan+    -- Construct a 'Closure' that effectively captures a value+    -- and represents a partially applied function.+    -- The 'static' keyword is used to convert a lambda,+    -- that doesn't capture any free variables, into a 'StaticPtr'.+    -- Then we use 'staticMap' to partially apply the lambda within the closure.+    let three = SI 3+        c = static (\(SI a) b -> a + b)+          `staticMap` cpure closureDict three+        request = encode $ CallClosure c 4+    writeChan serverChan (request, clientChan)+    result <- decode <$> readChan clientChan+    putStrLn $ "3 + 4 = " ++ show (result :: Maybe Int)
src/Control/Distributed/Closure.hs view
@@ -11,6 +11,7 @@ {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StaticPointers #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} #if __GLASGOW_HASKELL__ >= 800@@ -39,6 +40,7 @@ import Control.Distributed.Closure.Internal import Data.Binary (Binary) import Data.Constraint (Dict(..))+import Data.Typeable (Typeable)  -- $static-dicts --@@ -58,6 +60,13 @@ -- 'Control.Distributed.Closure.TH.withStatic' if it becomes too tedious. class c => Static c where   closureDict :: Closure (Dict c)++instance (Static c1, Static c2, Typeable c1, Typeable c2, (c1, c2)) => Static (c1, c2) where+  closureDict = static pairDict `cap` closureDict `cap` closureDict++-- Needs to be defined at top-level for GHC <8.4 compat.+pairDict :: Dict c1 -> Dict c2 -> Dict (c1, c2)+pairDict Dict Dict = Dict  -- | A newtype-wrapper useful for defining instances of classes indexed by -- higher-kinded types.
src/Control/Distributed/Closure/TH.hs view
@@ -92,7 +92,9 @@ -- @ -- -- You will probably want to enable @FlexibleContexts@ and @ScopedTypeVariables@--- in modules that use 'withStatic'.+-- in modules that use 'withStatic'. 'withStatic' can also handle non-user+-- generated instances like 'Typeable' instances: just write @instance Typeable+-- T@. withStatic :: TH.DecsQ -> TH.DecsQ withStatic = (>>= go)   where@@ -140,5 +142,9 @@         let staticins = TH.InstanceD staticcxt statichd methods #endif         decls' <- go decls-        return (ins : sigf : declf : staticins : decls')+        case hd of+          TH.AppT (TH.ConT nm) _ | nm == ''Typeable ->+            return (sigf : declf : staticins : decls')+          _ ->+            return (ins : sigf : declf : staticins : decls')     go (decl:decls) = (decl:) <$> go decls
tests/test.hs view
@@ -19,7 +19,7 @@ import GHC.StaticPtr import Test.Hspec import Test.Hspec.QuickCheck-import Test.QuickCheck+import qualified Test.QuickCheck as QC  data T a = T a data T1 a b = T1 a b@@ -38,16 +38,16 @@ -- * Basic generators (parameterized by size)  -- | Generates a basic closure using @cpure@-genPure :: forall a. (Static (Serializable a), Arbitrary a) => Int -> Gen (Closure a)+genPure :: forall a. (Static (Serializable a), QC.Arbitrary a) => Int -> QC.Gen (Closure a) genPure i =     cpure (closureDict :: Closure (Dict (Serializable a))) <$>-      resize (max 0 (i-1)) arbitrary+      QC.resize (max 0 (i-1)) QC.arbitrary  -- | Generates a basic closure using @closure@-genStatic :: Arbitrary (StaticPtr a) => Int -> Gen (Closure a)+genStatic :: QC.Arbitrary (StaticPtr a) => Int -> QC.Gen (Closure a) -- static pointers are considered to contribute 0 to the size, hence ignore the -- size parameter.-genStatic _i = closure <$> arbitrary+genStatic _i = closure <$> QC.arbitrary  -- | Reifies basic datatypes (they must be @Serializable@ types). Only two types -- here because we already have to enumerate a lot of cases manually (see below).@@ -55,18 +55,18 @@   TInt :: Type Int   TBool :: Type Bool -instance Static (Serializable Int) where-  closureDict = static Dict+instance Static (Binary Int) where closureDict = static Dict+instance Static (Typeable Int) where closureDict = static Dict -instance Static (Serializable Bool) where-  closureDict = static Dict+instance Static (Binary Bool) where closureDict = static Dict+instance Static (Typeable Bool) where closureDict = static Dict  -- | Existentially quantified version of 'Type'. So that they can be generated. data AType where AType :: Typeable a => Type a -> AType -instance Arbitrary (AType) where+instance QC.Arbitrary (AType) where   arbitrary =-      elements [ AType TInt, AType TBool ]+      QC.elements [ AType TInt, AType TBool ]  -- | Composed types. Very few choices because of the combinatorics. data Sig a where@@ -82,7 +82,7 @@ push _ (Two _ _ _) = Nothing  -- | Non-recursive generator of atomic values for each type.-genSimple :: Sig a -> Int -> Gen (Closure a)+genSimple :: Sig a -> Int -> QC.Gen (Closure a) genSimple (Zero TInt) = genPure genSimple (Zero TBool) = genPure genSimple (One TInt TInt) = genStatic@@ -100,14 +100,14 @@  gflip   :: (Typeable a, Typeable b, Typeable c)-  => (Int -> Gen (Closure (a->b->c))) -> Int -> Gen (Closure (b->a->c))+  => (Int -> QC.Gen (Closure (a->b->c))) -> Int -> QC.Gen (Closure (b->a->c)) gflip g i = (cap (static flip)) <$> g i  gap   :: Typeable a-  => (Int -> Gen (Closure (a->b)))-  -> (Int -> Gen (Closure a))-  -> Int -> Gen (Closure b)+  => (Int -> QC.Gen (Closure (a->b)))+  -> (Int -> QC.Gen (Closure a))+  -> Int -> QC.Gen (Closure b) gap gf gx i = do   f <- gf i   x <- gx i@@ -115,18 +115,18 @@  -- | Generate closures of a given type by randomly choosing to make the closure -- a 'cap'. Stays within the boundaries of 'Sig' so that the type of the--- function is also 'Arbitrary'.-genClosure :: Sig a -> Int -> Gen (Closure a)+-- function is also 'QC.Arbitrary'.+genClosure :: Sig a -> Int -> QC.Gen (Closure a) genClosure sig size | size < 10 =     genSimple sig size genClosure sig size = do-    stop <- frequency [(2, return True), (1, return False)]+    stop <- QC.frequency [(2, return True), (1, return False)]     if stop then       genSimple sig size     else do       let upper = div size 3           lower = max 0 (size - 1 - upper)-      AType pivot <- arbitrary+      AType pivot <- QC.arbitrary       case push pivot sig of         Nothing -> genSimple sig size         Just sig' -> do@@ -141,30 +141,30 @@ -- Must be from explicit lists since static pointers are, well, static. The -- combinatorics is unpleasant. -instance Arbitrary (StaticPtr (Int -> Int)) where+instance QC.Arbitrary (StaticPtr (Int -> Int)) where   arbitrary =-      elements+      QC.elements         [ static id         , static pred         , static succ         , static (3*)         ] -instance Arbitrary (StaticPtr (Bool -> Int)) where+instance QC.Arbitrary (StaticPtr (Bool -> Int)) where   arbitrary =-      elements+      QC.elements        [ static (bool 0 1)        , static (bool 57 42)] -instance Arbitrary (StaticPtr (Bool -> Bool)) where+instance QC.Arbitrary (StaticPtr (Bool -> Bool)) where   arbitrary =-    elements+    QC.elements       [ static id       , static not ] -instance Arbitrary (StaticPtr (Int -> Int -> Int)) where+instance QC.Arbitrary (StaticPtr (Int -> Int -> Int)) where   arbitrary =-      elements+      QC.elements         [ static const         , static (+)         , static (*)@@ -172,24 +172,24 @@         , static (\x y -> 2*x + y)         ] -instance Arbitrary (StaticPtr (Int -> Bool -> Int)) where+instance QC.Arbitrary (StaticPtr (Int -> Bool -> Int)) where   arbitrary =-    elements+    QC.elements       [ static const       , static (\n b -> if b then n else -n)       , static (bool 0)       ] -instance Arbitrary (StaticPtr (Bool -> Bool -> Int)) where+instance QC.Arbitrary (StaticPtr (Bool -> Bool -> Int)) where   arbitrary =-    elements+    QC.elements       [ static (\x y -> bool 0 1 (x&&y))       , static (\x y -> bool 57 42 (x||y))       ] -instance Arbitrary (StaticPtr (Int -> Int -> Bool)) where+instance QC.Arbitrary (StaticPtr (Int -> Int -> Bool)) where   arbitrary =-    elements+    QC.elements       [ static (==)       , static (>=)       , static (<=)@@ -197,28 +197,28 @@       , static (>)       ] -instance Arbitrary (StaticPtr (Bool -> Int -> Bool)) where+instance QC.Arbitrary (StaticPtr (Bool -> Int -> Bool)) where   arbitrary =-    elements+    QC.elements       [ static const       , static (\b n -> b && (n >= 0))       , static (\b n -> b || (n < 0))       , static (\b n -> if b then n >=0 else n < 0)       ] -instance Arbitrary (StaticPtr (Bool -> Bool -> Bool)) where+instance QC.Arbitrary (StaticPtr (Bool -> Bool -> Bool)) where   arbitrary =-    elements+    QC.elements       [ static (&&)       , static (||)]  -- * Instances -instance Arbitrary (Closure Int) where-  arbitrary = sized $ genClosure (Zero TInt)+instance QC.Arbitrary (Closure Int) where+  arbitrary = QC.sized $ genClosure (Zero TInt) -instance Arbitrary (Closure (Int -> Int)) where-  arbitrary = sized $ genClosure (One TInt TInt)+instance QC.Arbitrary (Closure (Int -> Int)) where+  arbitrary = QC.sized $ genClosure (One TInt TInt)  instance Show (Closure a) where   show _ = "<closure>"