remote-0.1: examples/tests/Test-Closure.hs
{-# LANGUAGE TemplateHaskell #-}
module Main where
-- This file contains some examples of closures, which are the
-- way to express a function invocation in Cloud Haskell. You
-- need to use closures to run code on a remote system, which is,
-- after all, the whole point. See the documentation on the function
-- 'remotable' for more details. Understanding this example is
-- essential to effectively using Cloud Haskell.
import Remote
import Remote.Call (mkClosure)
import Data.List (sort)
-- Step #1: Define the functions that we would
-- like to call remotely. These are just regular
-- functions.
sayHi :: String -> ProcessM ()
sayHi s = say $ "Greetings, " ++ s
sayHiPM :: String -> ProcessM String
sayHiPM s = say ("Hello, "++s) >>
return (sort s)
sayHiIO :: String -> IO String
sayHiIO s = putStrLn ("Hello, " ++ s) >>
return (reverse s)
sayHiPure :: String -> String
sayHiPure s = "Hello, " ++ s
simpleSum :: Int -> Int -> Int
simpleSum a b = a + b + 1
-- You can also manually call a closure using invokeClosure.
-- This is what functions like spawn and callRemote do
-- internally.
runAnother :: Closure Int -> ProcessM String
runAnother c = do mi <- invokeClosure c
case mi of
Just i -> return $ concat $ replicate i "Starscream"
Nothing -> return "No good"
-- This a partial closure: some parameters are provided by the caller,
-- and some are provided after the closure is invoked. You have to
-- write the function in a funny way to make this work automatically,
-- but otherwise it's pretty straightforward.
funnyHi :: String -> ProcessM (Int -> ProcessM ())
funnyHi s = return $ \i -> say ("Hello, " ++ (concat $ replicate i (reverse s)))
-- Step #2: Automagically generate closures
-- for these functions using remotable. For each
-- given function n, remotable will create a
-- closure for that function named n__closure.
-- You can then use that closure with spawn, remoteCall,
-- and invokeClosure. See examples below.
remotable ['sayHi, 'sayHiIO,'sayHiPure, 'sayHiPM, 'funnyHi, 'runAnother, 'simpleSum]
initialProcess _ = do
mynid <- getSelfNode
-- spawn and callRemote (and their variants) run
-- a function on a given node. We indicate which
-- node by giving a node ID (to keep it simple
-- we do everything on one node in this
-- example), and we indicate which function to run
-- by providing its closure.
-- A simple spawn. Does not block, and the result
-- we get back is the PID of the new process.
p <- spawn mynid (sayHi__closure "Zoltan")
say $ "Got result " ++ show p
-- callRemote is like a synchronous version of spawn.
-- It will block until the function ends, and returns
-- its result.
v <- callRemote mynid ( sayHiPM__closure "Jaroslav")
say $ "Got result " ++ v
-- We need a different function to call closures in the
-- IO monad. Also, instead of using the "something__closure"
-- syntax, you can call the Template Haskell mkClosure
-- function, which expands to the same thing.
w <- callRemoteIO mynid ( $(mkClosure 'sayHiIO) "Noodle")
say $ "Got result " ++ show w
-- Yet another version of callRemote for nonmonadic functions.
q <- callRemotePure mynid (sayHiPure__closure "Spatula")
say $ "Got result " ++ show q
-- We can even give closures to closures. They can in turn run
-- them indirectly (with spawn or callRemote) or directly
-- (with invokeClosure).
x <- callRemote mynid (runAnother__closure (simpleSum__closure 1 1))
say $ "Got result " ++ x
-- This function takes some parameters after closure invocation.
-- So the value we get back from invokeClosure is actually
-- a partially evaluated function.
mfunnyFun <- invokeClosure (funnyHi__closure "Antwerp")
case mfunnyFun of
Just funnyFun -> funnyFun 3
Nothing -> say "No good"
return ()
main = remoteInit (Just "config") [Main.__remoteCallMetaData] initialProcess