Tutorial: Follow along with code examples to learn Cloud Haskell.
+
+
+ $body$
+
+
+
+
+
+
+
+
diff --git a/tutorials/1ch.md b/tutorials/1ch.md
index 4eb07d2..9208224 100644
--- a/tutorials/1ch.md
+++ b/tutorials/1ch.md
@@ -26,9 +26,9 @@ and [GitHub](https://github.com).
Starting a new Cloud Haskell project using `stack` is as easy as
-{% highlight bash %}
+```bash
$ stack new
-{% endhighlight %}
+```
in a fresh new directory. This will populate the directory with
a number of files, chiefly `stack.yaml` and `*.cabal` metadata files
@@ -48,22 +48,22 @@ types that Cloud Haskell needs at a minimum in order to run.
In `app/Main.hs`, we start with our imports:
-{% highlight haskell %}
+```haskell
import Network.Transport.TCP (createTransport, defaultTCPParameters)
import Control.Distributed.Process
import Control.Distributed.Process.Node
-{% endhighlight %}
+```
Our TCP network transport backend needs an IP address and port to get started
with:
-{% highlight haskell %}
+```haskell
main :: IO ()
main = do
Right t <- createTransport "127.0.0.1" "10501" defaultTCPParameters
node <- newLocalNode t initRemoteTable
....
-{% endhighlight %}
+```
And now we have a running node.
@@ -75,7 +75,7 @@ a `Process` action to run, because our concurrent code will run in the
id can be used to send messages to the running process - here we will send one
to ourselves!
-{% highlight haskell %}
+```haskell
-- in main
_ <- runProcess node $ do
-- get our own process id
@@ -84,7 +84,7 @@ to ourselves!
hello <- expect :: Process String
liftIO $ putStrLn hello
return ()
-{% endhighlight %}
+```
Note that we haven't deadlocked our own thread by sending to and receiving
from its mailbox in this fashion. Sending messages is a completely
@@ -100,7 +100,7 @@ there is. Messages in the mailbox are ordered by time of arrival.
Let's spawn two processes on the same node and have them talk to each other:
-{% highlight haskell %}
+```haskell
import Control.Concurrent (threadDelay)
import Control.Monad (forever)
import Control.Distributed.Process
@@ -141,7 +141,7 @@ main = do
-- Without the following delay, the process sometimes exits before the messages are exchanged.
liftIO $ threadDelay 2000000
-{% endhighlight %}
+```
Note that we've used `receiveWait` this time around to get a message.
`receiveWait` and similarly named functions can be used with the
@@ -166,10 +166,10 @@ again.
Processes may send any datum whose type implements the `Serializable`
typeclass, defined as:
-{% highlight haskell %}
+```haskell
class (Binary a, Typeable) => Serializable a
instance (Binary a, Typeable a) => Serializable a
-{% endhighlight %}
+```
That is, any type that is `Binary` and `Typeable` is `Serializable`. This is
the case for most of Cloud Haskell's primitive types as well as many standard
@@ -177,14 +177,14 @@ data types. For custom data types, the `Typeable` instance is always
given by the compiler, and the `Binary` instance can be auto-generated
too in most cases, e.g.:
-{% highlight haskell %}
+```haskell
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
data T = T Int Char deriving (Generic, Typeable)
instance Binary T
-{% endhighlight %}
+```
### Spawning Remote Processes
@@ -212,9 +212,9 @@ Static actions are not easy to construct by hand, but fortunately Cloud
Haskell provides a little bit of Template Haskell to help. If `f :: T1 -> T2`
then
-{% highlight haskell %}
+```haskell
$(mkClosure 'f) :: T1 -> Closure T2
-{% endhighlight %}
+```
You can turn any top-level unary function into a `Closure` using `mkClosure`.
For curried functions, you'll need to uncurry them first (i.e. "tuple up" the
@@ -228,27 +228,27 @@ We need to configure our remote table (see the [API reference][6] for
more details) and the easiest way to do this, is to let the library
generate the relevant code for us. For example:
-{% highlight haskell %}
+```haskell
sampleTask :: (TimeInterval, String) -> Process ()
sampleTask (t, s) = sleep t >> say s
remotable ['sampleTask]
-{% endhighlight %}
+```
The last line is a top-level Template Haskell splice. At the call site for
`spawn`, we can construct a `Closure` corresponding to an application of
`sampleTask` like so:
-{% highlight haskell %}
+```haskell
($(mkClosure 'sampleTask) (seconds 2, "foobar"))
-{% endhighlight %}
+```
The call to `remotable` implicitly generates a remote table by inserting
a top-level definition `__remoteTable :: RemoteTable -> RemoteTable` in our
module for us. We compose this with other remote tables in order to come up
with a final, merged remote table for all modules in our program:
-{% highlight haskell %}
+```haskell
{-# LANGUAGE TemplateHaskell #-}
import Control.Concurrent (threadDelay)
@@ -275,7 +275,7 @@ main = do
_ <- spawnLocal $ sampleTask (1 :: Int, "using spawnLocal")
pid <- spawn us $ $(mkClosure 'sampleTask) (1 :: Int, "using spawn")
liftIO $ threadDelay 2000000
-{% endhighlight %}
+```
In the above example, we spawn `sampleTask` on node `us` in two
different ways:
diff --git a/tutorials/2ch.md b/tutorials/2ch.md
index 280eb12..1bb66c3 100644
--- a/tutorials/2ch.md
+++ b/tutorials/2ch.md
@@ -23,7 +23,7 @@ node names/addresses, or by using some form of registrar such as DNS-SD/Bonjour)
Here is an example program built against the [`simplelocalnet`][1] backend, that periodically
searches for a list of peer nodes, and sends a message to a registered (named) process on each.
-{% highlight haskell %}
+```haskell
import System.Environment (getArgs)
import Control.Distributed.Process
import Control.Distributed.Process.Node (initRemoteTable, runProcess)
@@ -38,7 +38,7 @@ main = do
peers <- findPeers backend 1000000
runProcess node $ forM_ peers $ \peer -> nsendRemote peer "echo-server" "hello!"
-{% endhighlight %}
+```
Clearly the program isn't very useful, but it illustrates the two key concepts that
`simplelocalnet` relies on. Firstly, that we `initializeBackend` in order to get
@@ -52,7 +52,7 @@ Here we simply rehash the controller/worker example from the `simplelocalnet` do
With the same imports as the example above, we add a no-op worker and a controller that
takes a list of its (known) workers, which it prints out before terminating them all.
-{% highlight haskell %}
+```haskell
main :: IO ()
main = do
args <- getArgs
@@ -65,18 +65,18 @@ main = do
backend <- initializeBackend host port initRemoteTable
startSlave backend
-{% endhighlight %}
+```
And the controller node is defined thus:
-{% highlight haskell %}
+```haskell
controller :: Backend -> [NodeId] -> Process ()
controller backend workers = do
-- Do something interesting with the workers
liftIO . putStrLn $ "Workers: " ++ show workers
-- Terminate the workers when the controller terminates (this is optional)
terminateAllSlaves backend
-{% endhighlight %}
+```
### Other Topologies and Backends
@@ -88,7 +88,7 @@ discovers and maintains knowledge of it's peers.
Here is an example of node discovery using the [`distributed-process-p2p`][3]
backend:
-{% highlight haskell %}
+```haskell
import System.Environment (getArgs)
import Control.Distributed.Process
import Control.Distributed.Process.Node (initRemoteTable)
@@ -103,7 +103,7 @@ main = do
runProcess node $ forever $ do
findPeers >>= mapM_ $ \peer -> nsend peer "echo-server" "hello!"
-{% endhighlight %}
+```
[1]: http://hackage.haskell.org/package/distributed-process-simplelocalnet
[2]: http://hackage.haskell.org/package/distributed-process-azure
diff --git a/tutorials/3ch.md b/tutorials/3ch.md
index 175dda6..52d1b4b 100644
--- a/tutorials/3ch.md
+++ b/tutorials/3ch.md
@@ -52,7 +52,7 @@ scanning the mailbox, it is dequeued and returned, otherwise the caller
(i.e., the calling thread/process) is blocked until a message of the expected
type is delivered to the mailbox. Let's take a look at this in action:
-{% highlight haskell %}
+```haskell
demo :: Process ()
demo = do
listener <- spawnLocal listen
@@ -69,7 +69,7 @@ demo = do
(say . show) second
(say . show) third
send third ()
-{% endhighlight %}
+```
This program will print `"hello"`, then `Nothing` and finally `pid://...`.
The first `expect` - labelled "third" because of the order in which we
@@ -92,13 +92,13 @@ whole `receive` expression evaluates to.
Consider the following snippet:
-{% highlight haskell %}
+```haskell
usingReceive = do
() <- receiveWait [
match (\(s :: String) -> say s)
, match (\(i :: Int) -> say $ show i)
]
-{% endhighlight %}
+```
Note that each of the matches in the list must evaluate to the same type,
as the type signature indicates: `receiveWait :: [Match b] -> Process b`.
@@ -125,26 +125,26 @@ simply dequeues _any_ messages it receives and forwards them to some other proce
In order to dequeue messages regardless of their type, this code relies on the
`matchAny` primitive, which has the following type:
-{% highlight haskell %}
+```haskell
matchAny :: forall b. (Message -> Process b) -> Match b
-{% endhighlight %}
+```
Since forwarding _raw messages_ (without decoding them first) is a common pattern
in Cloud Haskell programs, there is also a primitive to do that for us:
-{% highlight haskell %}
+```haskell
forward :: Message -> ProcessId -> Process ()
-{% endhighlight %}
+```
Given these types, we can see that in order to combine `matchAny` with `forward`
we need to either _flip_ `forward` and apply the `ProcessId` (leaving us with
the required type `Message -> Process b`) or use a lambda - the actual implementation
does the latter and looks like this:
-{% highlight haskell %}
+```haskell
relay :: ProcessId -> Process ()
relay !pid = forever' $ receiveWait [ matchAny (\m -> forward m pid) ]
-{% endhighlight %}
+```
This is pretty useful, but since `matchAny` operates on the raw `Message` type,
we're limited in what we can do with the messages we receive. In order to delve
@@ -154,11 +154,11 @@ the result to see whether the decoding succeeds or not. There are two primitives
we can use to that effect: `unwrapMessage` and `handleMessage`. Their types look like
this:
-{% highlight haskell %}
+```haskell
unwrapMessage :: forall m a. (Monad m, Serializable a) => Message -> m (Maybe a)
handleMessage :: forall m a b. (Monad m, Serializable a) => Message -> (a -> m b) -> m (Maybe b)
-{% endhighlight %}
+```
Of the two, `unwrapMessage` is the simpler, taking a raw `Message` and evaluating to
`Maybe a` before returning that value in the monad `m`. If the type of the raw `Message`
@@ -175,9 +175,9 @@ evaluates some input of type `a` and returns `Process Bool`, allowing us to run
`Process` code in order to decide whether or not the `a` is eligible to be forwarded to
the relay `ProcessId`. The type of `proxy` is thus:
-{% highlight haskell %}
+```haskell
proxy :: Serializable a => ProcessId -> (a -> Process Bool) -> Process ()
-{% endhighlight %}
+```
Since `matchAny` operates on `(Message -> Process b)` and `handleMessage` operates on
`a -> Process b` we can compose these to make our proxy server. We must not forward
@@ -185,7 +185,7 @@ messages for which the predicate function evaluates to `Just False`, nor can we
forward messages which the predicate function is unable to evaluate due to type
incompatibility. This leaves us with the definition found in distributed-process:
-{% highlight haskell %}
+```haskell
proxy pid proc = do
receiveWait [
matchAny (\m -> do
@@ -196,7 +196,7 @@ proxy pid proc = do
Nothing -> return ()) -- un-routable / cannot decode
]
proxy pid proc
-{% endhighlight %}
+```
Beyond simple relays and proxies, the raw message handling capabilities available in
distributed-process can be utilised to develop highly generic message processing code.
@@ -246,7 +246,7 @@ be busy processing other events. On the other hand, the [`die`][7] primitive thr
In practise, this means the following two functions could behave quite differently at
runtime:
-{% highlight haskell %}
+```haskell
-- this will never print anything...
demo1 = die "Boom" >> expect >>= say
@@ -256,7 +256,7 @@ demo2 = do
self <- getSelfPid
exit self "Boom"
expect >>= say
-{% endhighlight %}
+```
The `ProcessExitException` type holds a _reason_ field, which is serialised as a raw `Message`.
This exception type is exported, so it is possible to catch these _exit signals_ and decide how
@@ -283,14 +283,14 @@ of the type it is waiting for). Even though the child terminates "normally", our
is also terminated since `link` will _link the lifetime of two processes together_ regardless
of exit reasons.
-{% highlight haskell %}
+```haskell
demo = do
pid <- spawnLocal $ expect >>= return
link pid
send pid ()
() <- expect
return ()
-{% endhighlight %}
+```
The medium that link failures uses to signal exit conditions is the same as exit and kill
signals - asynchronous exceptions. Once again, it is a bad idea to rely on this (not least
@@ -312,7 +312,7 @@ monitors can be used to determine both when and _how_ a process has terminated.
away in distributed-process-extras, the `linkOnFailure` primitive works in exactly this
way, only terminating the caller if the subject terminates abnormally. Let's take a look...
-{% highlight haskell %}
+```haskell
linkOnFailure them = do
us <- getSelfPid
tid <- liftIO $ myThreadId
@@ -330,7 +330,7 @@ linkOnFailure them = do
case reason of
DiedNormal -> return ()
_ -> liftIO $ throwTo tid (ProcessLinkException us reason)
-{% endhighlight %}
+```
As we can see, this code makes use of monitors to track both processes involved in the
link. In order to track _both_ processes and react to changes in their status, it is
@@ -384,7 +384,7 @@ This example is a bit contrived and over-simplified but
illustrates the concept. Consider the `fetchUser` function below, it runs in the `AppProcess`
monad which provides the configuration settings required to connect to the database:
-{% highlight haskell %}
+```haskell
import Data.ByteString (ByteString)
import Control.Monad.Reader
@@ -414,7 +414,7 @@ openDB = do
closeDB :: DB.Connection -> AppProcess ()
closeDB db = liftIO (DB.close db)
-{% endhighlight %}
+```
So this would mostly work but it is not complete. What happens if an exception
is thrown by the `query` function? Your open database handle may not be
@@ -422,14 +422,14 @@ closed. Typically we manage this with the [bracket][brkt] function.
In the base library, [bracket][brkt] is defined in Control.Exception with this signature:
-{% highlight haskell %}
+```haskell
bracket :: IO a --^ computation to run first ("acquire resource")
-> (a -> IO b) --^ computation to run last ("release resource")
-> (a -> IO c) --^ computation to run in-between
-> IO c
-{% endhighlight %}
+```
Great! We pass an IO action that acquires a resource; `bracket` passes that
resource to a function which takes the resource and runs another action.
@@ -447,7 +447,7 @@ It is perfectly possible to write our own bracket; `distributed-process` does th
for the `Process` monad (which is itself a newtyped ReaderT stack). Here is how that is done:
-{% highlight haskell %}
+```haskell
-- | Lift 'Control.Exception.bracket'
bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
bracket before after thing =
@@ -473,7 +473,7 @@ mask p = do
onException :: Process a -> Process b -> Process a
onException p what = p `catch` \e -> do _ <- what
liftIO $ throwIO (e :: SomeException)
-{% endhighlight %}
+```
`distributed-process` needs to do this sort of thing to keep its dependency
list small, but do we really want to write this for every transformer stack
@@ -491,7 +491,7 @@ explanation written by Michael Snoyman which is available [here][mctrlt].
in the Haskell base library. For example, [Control.Exception.Lifted][lexc] has a definition of
bracket that looks like this:
-{% highlight haskell %}
+```haskell
bracket :: MonadBaseControl IO m
=> m a --^ computation to run first ("acquire resource")
@@ -499,7 +499,7 @@ bracket :: MonadBaseControl IO m
-> (a -> m c) --^ computation to run in-between
-> m c
-{% endhighlight %}
+```
It is just the same as the version found in base, except it is generalized to work
with actions in any monad that implements [MonadBaseControl IO][mbc]. [monad-control][mctrl] defines
@@ -511,7 +511,7 @@ provides orphan instances of the `Process` type for both [MonadBase IO][mb] and
After importing these, we can rewrite our `fetchUser` function to use the instance of bracket
provided by [lifted-base][lbase].
-{% highlight haskell %}
+```haskell
-- ...
import Control.Distributed.Process.MonadBaseControl ()
@@ -526,7 +526,7 @@ fetchUser email =
$ \db -> liftIO $ DB.query db email
-{% endhighlight %}
+```
[lifted-base][lbase] also provides conveniences like [MVar][lmvar] and other concurrency primitives that
operate in [MonadBase IO][mb]. One benefit here is that your code is not sprinkled with
diff --git a/tutorials/4ch.md b/tutorials/4ch.md
index fddc1d4..25f3556 100644
--- a/tutorials/4ch.md
+++ b/tutorials/4ch.md
@@ -38,7 +38,7 @@ also provides hooks for error handling (in case of either server code crashing
_or_ exit signals dispatched to the server process from elsewhere) and _cleanup_
code to be run on termination/shutdown.
-{% highlight haskell %}
+```haskell
myServer :: ProcessDefinition MyStateType
myServer =
ProcessDefinition {
@@ -75,7 +75,7 @@ myServer =
, unhandledMessagePolicy = Drop -- Terminate | (DeadLetter ProcessId)
}
-{% endhighlight %}
+```
When defining a protocol between client and server, we typically decide on
a set of types the server will handle and possibly maps these to replies we
@@ -112,7 +112,7 @@ that math server that does just that:
----
-{% highlight haskell %}
+```haskell
module MathServer
( -- client facing API
add
@@ -151,7 +151,7 @@ launchMathServer =
, unhandledMessagePolicy = Drop
}
in spawnLocal $ serve () (statelessInit Infinity) server >> return ()
-{% endhighlight %}
+```
This style of programming will already be familiar if you've used some
@@ -177,7 +177,7 @@ make the server code any prettier (since it has to reply to the channel
explicitly, rather than just evaluating to a result), it does reduce the
likelihood of runtime errors somewhat.
-{% highlight haskell %}
+```haskell
-- This is the only way clients can get a message through to us that
-- we will respond to, and since we control the type(s), there is no
-- risk of decoding errors on the server. The /call/ API ensures that
@@ -194,7 +194,7 @@ launchMathServer =
, unhandledMessagePolicy = Drop
}
in spawnLocal $ serve () (statelessInit Infinity) server >> return ()
-{% endhighlight %}
+```
Ensuring that only valid types are sent to the server is relatively simple,
given that we do not expose the client directly to `call` and write our own
@@ -220,7 +220,7 @@ this function in a test/demo application, you'll need to block the main
thread for a while to wait for the server to receive the message and print
out the result.
-{% highlight haskell %}
+```haskell
printSum :: ProcessId -> Double -> Double -> Process ()
printSum sid = cast sid . Add
@@ -233,7 +233,7 @@ launchMathServer =
, unhandledMessagePolicy = Drop
}
in spawnLocal $ serve () (statelessInit Infinity) server >> return ()
-{% endhighlight %}
+```
Of course this is a toy example - why defer simple computations like addition
@@ -278,7 +278,7 @@ manner suits them: The type of a task will be `Closure (Process a)` and
the server will explicitly return an /either/ value with `Left String`
for errors and `Right a` for successful results.
-{% highlight haskell %}
+```haskell
-- enqueues the task in the pool and blocks
-- the caller until the task is complete
executeTask :: forall s a . (Addressable s, Serializable a)
@@ -286,7 +286,7 @@ executeTask :: forall s a . (Addressable s, Serializable a)
-> Closure (Process a)
-> Process (Either String a)
executeTask sid t = call sid t
-{% endhighlight %}
+```
Remember that in Cloud Haskell, the only way to communicate with a process
(apart from introducing scoped concurrency primitives like `MVar` or using
@@ -337,9 +337,9 @@ run to completion) and communicate the result (or failure) to the original calle
This means our pool state will need to be parameterised by the result type it will
accept in its closures. So now we have the beginnings of our state type:
-{% highlight haskell %}
+```haskell
data BlockingQueue a = BlockingQueue
-{% endhighlight %}
+```
### Making use of Async
@@ -365,17 +365,17 @@ size limit), we hold the client ref and the closure, but no monitor ref. We'll
use a data structure that support FIFO ordering semantics for this, since that's
probably what clients will expect of something calling itself a "queue".
-{% highlight haskell %}
+```haskell
data BlockingQueue a = BlockingQueue {
poolSize :: SizeLimit
, active :: [(MonitorRef, CallRef (Either ExitReason a), Async a)]
, accepted :: Seq (CallRef (Either ExitReason a), Closure (Process a))
}
-{% endhighlight %}
+```
Our queue-like behaviour is fairly simple to define using `Data.Sequence`:
-{% highlight haskell %}
+```haskell
enqueue :: Seq a -> a -> Seq a
enqueue s a = a <| s
@@ -387,7 +387,7 @@ getR s =
case (viewr s) of
EmptyR -> Nothing
a -> Just a
-{% endhighlight %}
+```
Now, to turn that `Closure` environment into a thunk we can evaluate, we'll use the
@@ -397,11 +397,11 @@ the async API in detail here, except to point out that the call to `async` spawn
new process to do the actual work and returns a handle that we can use to query for
the result.
-{% highlight haskell %}
+```haskell
proc <- unClosure task'
asyncHandle <- async proc
ref <- monitorAsync asyncHandle
-{% endhighlight %}
+```
We can now implement the `acceptTask` function, which the server will use to handle
submitted tasks. The signature of our function must be compatible with the message
@@ -418,7 +418,7 @@ with a possible reply to one of the `call` derivatives. Since we're deferring ou
until later, we will use `noReply_`, which creates a `ProcessAction` for us, telling
the server to continue receiving messages.
-{% highlight haskell %}
+```haskell
storeTask :: Serializable a
=> BlockingQueue a
-> CallRef (Either ExitReason a)
@@ -442,7 +442,7 @@ acceptTask s@(BlockingQueue sz' runQueue taskQueue) from task' =
ref <- monitorAsync asyncHandle
let taskEntry = (ref, from, asyncHandle)
return s { active = (taskEntry:runQueue) }
-{% endhighlight %}
+```
If we're at capacity, we add the task (and caller) to the `accepted` queue,
otherwise we launch and monitor the task using `async` and stash the monitor
@@ -481,7 +481,7 @@ and since there's no expected reply, as with `cast`, we simply return a `Process
telling the server what to do next - in this case, to `continue` reading from the
mailbox.
-{% highlight haskell %}
+```haskell
taskComplete :: forall a . Serializable a
=> BlockingQueue a
-> ProcessMonitorNotification
@@ -521,7 +521,7 @@ deleteFromRunQueue :: (MonitorRef, CallRef (Either ExitReason a), Async a)
-> [(MonitorRef, CallRef (Either ExitReason a), Async a)]
-> [(MonitorRef, CallRef (Either ExitReason a), Async a)]
deleteFromRunQueue c@(p, _, _) runQ = deleteBy (\_ (b, _, _) -> b == p) c runQ
-{% endhighlight %}
+```
We've dealt with mapping the `AsyncResult` to `Either` values, which we *could* have
left to the caller, but this makes the client facing API much simpler to work with.
@@ -543,14 +543,14 @@ In order to spell things out for the compiler, we need to put a type signature
in place at the call site for `storeTask`, so our final construct for that
handler is thus:
-{% highlight haskell %}
+```haskell
handleCallFrom (\s f (p :: Closure (Process a)) -> storeTask s f p)
-{% endhighlight %}
+```
No such thing is required for `taskComplete`, as there's no ambiguity about its
type. Our process definition is now finished, and here it is:
-{% highlight haskell %}
+```haskell
defaultProcess {
apiHandlers = [
handleCallFrom (\s f (p :: Closure (Process a)) -> storeTask s f p)
@@ -558,7 +558,7 @@ defaultProcess {
]
, infoHandlers = [ handleInfo taskComplete ]
}
-{% endhighlight %}
+```
Starting the server takes a bit of work: `ManagedProcess` provides several
utility functions to help with spawning and running processes. The `serve`
@@ -567,7 +567,7 @@ that must generate the initial state and set up the server's receive timeout,
then the process definition which we've already encountered. For more details
about starting managed processes, see the haddocks.
-{% highlight haskell %}
+```haskell
run :: forall a . (Serializable a)
=> Process (InitResult (BlockingQueue a))
-> Process ()
@@ -585,25 +585,25 @@ pool :: forall a . Serializable a
=> SizeLimit
-> Process (InitResult (BlockingQueue a))
pool sz' = return $ InitOk (BlockingQueue sz' [] Seq.empty) Infinity
-{% endhighlight %}
+```
### Putting it all together
Defining tasks is as simple as making them remote-worthy:
-{% highlight haskell %}
+```haskell
sampleTask :: (TimeInterval, String) -> Process String
sampleTask (t, s) = sleep t >> return s
$(remotable ['sampleTask])
-{% endhighlight %}
+```
And executing them is just as simple too.
-{% highlight haskell %}
+```haskell
tsk <- return $ ($(mkClosure 'sampleTask) (seconds 2, "foobar"))
executeTask taskQueuePid tsk
-{% endhighlight %}
+```
Starting up the server itself locally or on a remote node, is just a matter of
combining `spawn` or `spawnLocal` with `start`. We can go a step further though,
@@ -621,17 +621,17 @@ able to pass this handle to the managed process `call` API, so we define an
instance of the `Resolvable` typeclass for it, which makes a (default) instance of
`Routable` available, which is exactly what `call` is expecting:
-{% highlight haskell %}
+```haskell
newtype TaskQueue a = TaskQueue { unQueue :: ProcessId }
instance Resolvable (TaskQueue a) where
resolve = return . unQueue
-{% endhighlight %}
+```
Finally, we write a `start` function that returns this handle and change the
signature of `executeTask` to match it:
-{% highlight haskell %}
+```haskell
start :: forall a . (Serializable a)
=> SizeLimit
-> Process (TaskQueue a)
@@ -644,7 +644,7 @@ executeTask :: (Serializable a)
-> Closure (Process a)
-> Process (Either ExitReason a)
executeTask sid t = call sid t
-{% endhighlight %}
+```
----------
diff --git a/tutorials/6ch.md b/tutorials/6ch.md
index 29a32c6..357fce7 100644
--- a/tutorials/6ch.md
+++ b/tutorials/6ch.md
@@ -55,7 +55,7 @@ that our clients only communicate with us in well-known ways. Let's take a
look at this in action, revisiting the well-trodden _math server_ example
from our previous tutorials:
-{% highlight haskell %}
+```haskell
module MathServer
( -- client facing API
MathServer()
@@ -82,7 +82,7 @@ launchMathServer = launch >>= return . MathServer
, unhandledMessagePolicy = Drop
}
in spawnLocal $ start () (statelessInit Infinity) server >> return ()
-{% endhighlight %}
+```
What we've changed here is the _handle_ clients use to communicate with the
process, hiding the `ProcessId` behind a newtype and forcing client code to
@@ -118,17 +118,17 @@ We can alleviate this problem using phantom type parameters, storing only
the real `ProcessId` we need to communicate with the server, whilst utilising
the compiler to ensure the correct types are assumed at both ends.
-{% highlight haskell %}
+```haskell
data Registry k v = Registry { registryPid :: ProcessId }
deriving (Typeable, Generic, Show, Eq)
instance (Keyable k, Serializable v) => Binary (Registry k v) where
-{% endhighlight %}
+```
In order to start our registry, we need to know the specific `k` and `v` types,
but we do not real values of these, so we use scoped type variables to reify
them when creating the `Registry` handle:
-{% highlight haskell %}
+```haskell
start :: forall k v. (Keyable k, Serializable v) => Process (Registry k v)
start = return . Registry =<< spawnLocal (run (undefined :: Registry k v))
@@ -136,7 +136,7 @@ run :: forall k v. (Keyable k, Serializable v) => Registry k v -> Process ()
run _ =
MP.pserve () (const $ return $ InitOk initState Infinity) serverDefinition
-- etc....
-{% endhighlight %}
+```
Having wrapped the `ProcessId` in a newtype that ensures the types with which
the server was initialised are respected by clients, we use the same approach
@@ -144,11 +144,11 @@ as earlier to force clients of our API to interact with the server not only
using the requisite call/cast protocol, but also providing the correct types
in the form of a valid handle.
-{% highlight haskell %}
+```haskell
addProperty :: (Keyable k, Serializable v)
=> Registry k v -> k -> v -> Process RegisterKeyReply
addProperty reg k v = ....
-{% endhighlight %}
+```
So long as we only expose `Registry` newtype construction via our `start` API,
clients cannot forge a registry handle and both client and server can rely on
@@ -175,10 +175,10 @@ are your friend. By providing a `Resolvable` instance, you can expose your
decision to only expose the `ProcessId` via a typeclass) the need to use the
handle in client code.
-{% highlight haskell %}
+```haskell
instance Resolvable (Registry k v) where
resolve = return . Just . registryPid
-{% endhighlight %}
+```
The [`Routable`][rtbl] typeclass provides a means to dispatch messages without
having to know the implementation details behind the scenes. This provides us
@@ -190,16 +190,16 @@ There is a default (and fairly efficient) instance of [`Routable`][rtbl] for all
[`Resolvable`][rsbl] instances, so it is usually enough to implement the latter.
An explicit implementation for our `Registry` would look like this:
-{% highlight haskell %}
+```haskell
instance Routable (Registry k v) where
sendTo reg msg = send (registryPid reg) msg
unsafeSendTo reg msg = unsafeSend (registryPid reg) msg
-{% endhighlight %}
+```
Similar typeclasses are provided for the many occaisions when you need to link
to or kill a process without knowing its `ProcessId`:
-{% highlight haskell %}
+```haskell
class Linkable a where
-- | Create a /link/ with the supplied object.
linkTo :: a -> Process ()
@@ -207,7 +207,7 @@ class Linkable a where
class Killable a where
killProc :: a -> String -> Process ()
exitProc :: (Serializable m) => a -> m -> Process ()
-{% endhighlight %}
+```
Again, there are default instances of both typeclasses for all [`Resolvable`][rsbl]
types, so it is enough to provide just that instance for your handles.
@@ -264,11 +264,11 @@ do, right up to monitoring the server for potential exit signals (so as not to
deadlock the client if the server dies before replying) - all of which is handled
by `awaitResponse` in the platform's `Primitives` module.
-{% highlight haskell %}
+```haskell
syncSafeCallChan server msg = do
rp <- callChan server msg
awaitResponse server [ matchChan rp (return . Right) ]
-{% endhighlight %}
+```
This might sound like a vast improvement on the usual combination of a client
API that uses `call` and a corresponding `handleCall` in the process definition,
@@ -280,7 +280,7 @@ so on. None of these features will work with the corollary family of
leave as a question for the reader to determine. The following example demonstrates
the use of reply channels:
-{% highlight haskell %}
+```haskell
-- two versions of the same handler, one for calls, one for typed (reply) channels
data State
@@ -304,7 +304,7 @@ callHandler = handleCall $ \state Input -> reply Output state
chanHandler :: Dispatcher State
chanHandler = handleRpcChan $ \state port Input -> replyChan port Output >> continue state
-{% endhighlight %}
+```
------
> ![Info: ][info] Using typed channels for replies is both flexible and efficient.
@@ -361,7 +361,7 @@ to the calling process, at least to some extent. For this example, we'll examine
[`Mailbox`][mailbox] module, since this combines a fire-and-forget control channel with
an opaque server handle.
-{% highlight haskell %}
+```haskell
-- our handle is fairly simple
data Mailbox = Mailbox { pid :: !ProcessId
, cchan :: !(ControlPort ControlMessage)
@@ -424,7 +424,7 @@ processDefinition pid tc cc = do
, handleRaw handleRawInputs ]
, unhandledMessagePolicy = DeadLetter pid
} :: Process (ProcessDefinition State)
-{% endhighlight %}
+```
Since the rest of the mailbox initialisation code is quite complex, we'll leave it
there for now. The important details to take away are the use of `chanServe`
@@ -448,7 +448,7 @@ since _that_ API only supports a single control channel - the original purpose b
the control channel concept - and instead, we'll create the process loop ourselves,
using the exported low level `recvLoop` function.
-{% highlight haskell %}
+```haskell
type NumRequests = Int
@@ -547,7 +547,7 @@ handleStats :: NumRequests -> StatsRequest -> Process (ProcessAction State)
handleStats count (StatsReq replyTo) = do
replyChan replyTo count
continue count
-{% endhighlight %}
+```
Although not very useful, this is a working example. Note that the client must
deal with a `ControlPort` and not the complete `ControlChannel` itself. Also
diff --git a/tutorials/tutorial-NT2.md b/tutorials/tutorial-NT2.md
index fabcd40..002b71f 100644
--- a/tutorials/tutorial-NT2.md
+++ b/tutorials/tutorial-NT2.md
@@ -34,20 +34,20 @@ echoed by the server back to the client.
Here is what it will look like. We can start the server on one host:
-{% highlight bash %}
+```bash
# ./tutorial-server 192.168.1.108 8080
Echo server started at "192.168.1.108:8080:0"
-{% endhighlight %}
+```
then start the client on another. The client opens a connection to the server,
sends "Hello world", and prints all the `Events` it receives:
-{% highlight bash %}
+```bash
# ./tutorial-client 192.168.1.109 8080 192.168.1.108:8080:0
ConnectionOpened 1024 ReliableOrdered "192.168.1.108:8080:0"
Received 1024 ["Hello world"]
ConnectionClosed 1024
-{% endhighlight %}
+```
The client receives three `Event`s:
@@ -66,21 +66,21 @@ We will start with the client
([tutorial-client.hs](https://github.com/haskell-distributed/distributed-process/blob/master/doc/tutorial/tutorial-client.hs)),
because it is simpler. We first need a bunch of imports:
-{% highlight haskell %}
+```haskell
import Network.Transport
import Network.Transport.TCP (createTransport, defaultTCPParameters)
import Network.Socket.Internal (withSocketsDo)
import System.Environment
import Data.ByteString.Char8
import Control.Monad
-{% endhighlight %}
+```
The client will consist of a single main function. [withSocketsDo](http://hackage.haskell.org/package/network-2.6.2.1/docs/Network-Socket-Internal.html#v:withSocketsDo) may be needed for Windows platform with old versions of network library. For compatibility with older versions on Windows, it is good practice to always call withSocketsDo (it's very cheap).
-{% highlight haskell %}
+```haskell
main :: IO ()
main = withSocketsDo $ do
-{% endhighlight %}
+```
When we start the client we expect three command line arguments.
Since the client will itself be a network endpoint, we need to know the IP
@@ -88,40 +88,40 @@ address and port number to use for the client. Moreover, we need to know the
endpoint address of the server (the server will print this address to the
console when it is started):
-{% highlight haskell %}
+```haskell
[host, port, serverAddr] <- getArgs
-{% endhighlight %}
+```
Next we need to initialize the Network.Transport layer using `createTransport`
from `Network.Transport.TCP` (in this tutorial we will use the TCP instance of
`Network.Transport`). The type of `createTransport` is:
-{% highlight haskell %}
+```haskell
createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
-{% endhighlight %}
+```
(where `N` is an alias for `Network.Socket`). For the sake of this tutorial we
are going to ignore all error handling, so we are going to assume it will return
a `Right` transport:
-{% highlight haskell %}
+```haskell
Right transport <- createTransport host port
-{% endhighlight %}
+```
Next we need to create an EndPoint for the client. Again, we are going
to ignore errors:
-{% highlight haskell %}
+```haskell
Right endpoint <- newEndPoint transport
-{% endhighlight %}
+```
Now that we have an endpoint we can connect to the server, after we convert
the `String` we got from `getArgs` to an `EndPointAddress`:
-{% highlight haskell %}
+```haskell
let addr = EndPointAddress (pack serverAddr)
Right conn <- connect endpoint addr ReliableOrdered defaultConnectHints
-{% endhighlight %}
+```
`ReliableOrdered` means that the connection will be reliable (no messages will be
lost) and ordered (messages will arrive in order). For the case of the TCP transport
@@ -130,33 +130,33 @@ not be true for other transports.
Sending on our new connection is very easy:
-{% highlight haskell %}
+```haskell
send conn [pack "Hello world"]
-{% endhighlight %}
+```
(`send` takes as argument an array of `ByteString`s).
Finally, we can close the connection:
-{% highlight haskell %}
+```haskell
close conn
-{% endhighlight %}
+```
Function `receive` can be used to get the next event from an endpoint. To print the
first three events, we can do
-{% highlight haskell %}
+```haskell
replicateM_ 3 $ receive endpoint >>= print
-{% endhighlight %}
+```
Since we're not expecting more than 3 events, we can now close the transport.
-{% highlight haskell %}
+```haskell
closeTransport transport
-{% endhighlight %}
+```
That's it! Here is the entire client again:
-{% highlight haskell %}
+```haskell
main :: IO ()
main = withSocketsDo $ do
[host, port, serverAddr] <- getArgs
@@ -171,7 +171,7 @@ main = withSocketsDo $ do
replicateM_ 3 $ receive endpoint >>= print
closeTransport transport
-{% endhighlight %}
+```
### Writing the server
@@ -179,7 +179,7 @@ The server ([tutorial-server.hs](https://github.com/haskell-distributed/distribu
is slightly more complicated, but only slightly. As with the client, we
start with a bunch of imports:
-{% highlight haskell %}
+```haskell
import Network.Transport
import Network.Transport.TCP (createTransport, defaultTCPParameters)
import Network.Socket.Internal (withSocketsDo)
@@ -187,11 +187,11 @@ import Control.Concurrent
import Data.Map
import Control.Exception
import System.Environment
-{% endhighlight %}
+```
We will write the main function first:
-{% highlight haskell %}
+```haskell
main :: IO ()
main = withSocketsDo $ do
[host, port] <- getArgs
@@ -201,7 +201,7 @@ main = withSocketsDo $ do
forkIO $ echoServer endpoint serverDone
putStrLn $ "Echo server started at " ++ show (address endpoint)
readMVar serverDone `onCtrlC` closeTransport transport
-{% endhighlight %}
+```
This is very similar to the `main` function for the client. We get the
hostname and port number that the server should use and create a transport
@@ -218,14 +218,14 @@ our connection to them.
`Event` is defined in `Network.Transport` as
-{% highlight haskell %}
+```haskell
data Event =
Received ConnectionId [ByteString]
| ConnectionClosed ConnectionId
| ConnectionOpened ConnectionId Reliability EndPointAddress
| EndPointClosed
...
-{% endhighlight %}
+```
(there are few other events, which we are going to ignore). `ConnectionId`s help us
distinguish messages sent on one connection from messages sent on another. In
@@ -240,7 +240,7 @@ Finally, when we receive the `EndPointClosed` message we signal to the main
thread that we are doing and terminate. We will receive this message when the
main thread calls `closeTransport` (that is, when the user presses Control-C).
-{% highlight haskell %}
+```haskell
echoServer :: EndPoint -> MVar () -> IO ()
echoServer endpoint serverDone = go empty
where
@@ -268,20 +268,20 @@ echoServer endpoint serverDone = go empty
EndPointClosed -> do
putStrLn "Echo server exiting"
putMVar serverDone ()
-{% endhighlight %}
+```
This implements almost exactly what we described above. The only complication is that we want to avoid blocking the receive queue; so for every message that comes in we spawn a new thread to deal with it. Since is therefore possible that we receive the `Received` event before an outgoing connection has been established, we map connection IDs to MVars containing connections.
Finally, we need to define `onCtrlC`; `p onCtrlC q` will run `p`; if this is interrupted by Control-C we run `q` and then try again:
-{% highlight haskell %}
+```haskell
onCtrlC :: IO a -> IO () -> IO a
p `onCtrlC` q = catchJust isUserInterrupt p (const $ q >> p `onCtrlC` q)
where
isUserInterrupt :: AsyncException -> Maybe ()
isUserInterrupt UserInterrupt = Just ()
isUserInterrupt _ = Nothing
-{% endhighlight %}
+```
### Conclusion
diff --git a/wiki.md b/wiki.md
index 675dede..66631f7 100644
--- a/wiki.md
+++ b/wiki.md
@@ -24,9 +24,9 @@ There is a makefile in the root directory which will create a wiki page for
you (in the wiki directory) and populate the front matter for you. Calling the
makefile is pretty easy.
-{% highlight bash %}
+```bash
make wikipage NAME=
-{% endhighlight %}
+```
[1]: https://github.com/mojombo/jekyll
[2]: https://github.com/haskell-distributed/haskell-distributed.github.com
diff --git a/wiki/contributing.md b/wiki/contributing.md
index 6612207..9511dde 100644
--- a/wiki/contributing.md
+++ b/wiki/contributing.md
@@ -69,7 +69,7 @@ make your changes in a local branch. Before submitting your pull request, fetch
and rebase any changes to the upstream source branch and merge these into your
local branch. For example:
-{% highlight bash %}
+```bash
## on your local repository, create a branch to work in
$ git checkout -b bugfix-issue123
@@ -89,7 +89,7 @@ $ git merge master
## make sure you resolve any merge conflicts
## and commit before sending a pull request!
-{% endhighlight %}
+```
### __3. Follow the patch submission *rules of thumb*__
@@ -188,7 +188,7 @@ quite frequently and it is pain keeping the indentation consistent.
The one exception to this is probably imports/exports, which we *are* a
bit finicky about:
-{% highlight haskell %}
+```haskell
import qualified Foo.Bar.Baz as Bz
import Data.Binary
( Binary (..),
@@ -197,7 +197,7 @@ import Data.Binary
)
import Data.Blah
import Data.Boom (Typeable)
-{% endhighlight %}
+```
We generally don't care *that much* about alignment for other things,
but as always, try to follow the convention in the file you're editing
@@ -216,14 +216,14 @@ Comment every top level function (particularly exported functions),
and provide a type signature; use Haddock syntax in the comments.
Comment every exported data type. Function example:
-{% highlight haskell %}
+```haskell
-- | Send a message on a socket. The socket must be in a connected
-- state. Returns the number of bytes sent. Applications are
-- responsible for ensuring that all data has been sent.
send :: Socket -- ^ Connected socket
-> ByteString -- ^ Data to send
-> IO Int -- ^ Bytes sent
-{% endhighlight %}
+```
For functions, the documentation should give enough information to
apply the function without looking at the function's definition.
diff --git a/wiki/networktransport.md b/wiki/networktransport.md
index f6fb51a..0938482 100644
--- a/wiki/networktransport.md
+++ b/wiki/networktransport.md
@@ -44,7 +44,7 @@ You may also submit issues on [github][8].
For a flavour of what programming with `Network.Transport` looks like, here is a tiny self-contained example.
-{% highlight haskell %}
+```haskell
import Network.Transport
import Network.Transport.TCP (createTransport, defaultTCPParameters)
import Control.Concurrent
@@ -84,7 +84,7 @@ main = do
conn <- takeMVar clientDone
close conn
takeMVar serverDone
-{% endhighlight %}
+```
We create a "server" and a "client" (each represented by an `EndPoint`).
The server waits for `Event`s and whenever it receives a message it just prints
@@ -166,14 +166,14 @@ A series of benchmarks has shown that
using `Data.Serialize` is very slow (and using Blaze.ByteString not much
better). This is fast:
-{% highlight haskell %}
+```haskell
foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
encodeLength :: Int32 -> IO ByteString
encodeLength i32 =
BSI.create 4 $ \p ->
pokeByteOff p 0 (htonl (fromIntegral i32))
-{% endhighlight %}
+```
* We do not need to use `blaze-builder` or related;
`Network.Socket.Bytestring.sendMany` uses vectored I/O. On the client side
@@ -202,7 +202,7 @@ to the Transport API.
We can either have this as part of the transport
-{% highlight haskell %}
+```haskell
data Transport = Transport {
...
, newMulticastGroup :: IO (Either Error MulticastGroup)
@@ -213,11 +213,11 @@ We can either have this as part of the transport
, multicastAddress :: MulticastAddress
, deleteMulticastGroup :: IO ()
}
-{% endhighlight %}
+```
or as part of an endpoint:
-{% highlight haskell %}
+```haskell
data Transport = Transport {
newEndPoint :: IO (Either Error EndPoint)
}
@@ -226,7 +226,7 @@ or as part of an endpoint:
...
, newMulticastGroup :: IO (Either Error MulticastGroup)
}
-{% endhighlight %}
+```
It should probably be part of the `Transport`, as there is no real connection
between an endpoint and the creation of the multigroup (however, see section
@@ -239,7 +239,7 @@ endpoint wants to receive events when multicast messages are sent.
We could reify a subscription:
-{% highlight haskell %}
+```haskell
data EndPoint = EndPoint {
...
, multicastSubscribe :: MulticastAddress -> IO MulticastSubscription
@@ -249,17 +249,17 @@ We could reify a subscription:
...
, multicastSubscriptionClose :: IO ()
}
-{% endhighlight %}
+```
but this suggests that one might have multiple subscriptions to the same group
which can be distinguished, which is misleading. Probably better to have:
-{% highlight haskell %}
+```haskell
data EndPoint = EndPoint {
multicastSubscribe :: MulticastAddress -> IO ()
, multicastUnsubscribe :: MulticastAddress -> IO ()
}
-{% endhighlight %}
+```
#### Sending messages to a multicast group
@@ -275,7 +275,7 @@ same multicast group, and if so, whether it is useful.
If we decide that multiple lightweight connections to the multigroup is useful,
one option might be
-{% highlight haskell %}
+```haskell
data EndPoint = EndPoint {
...
, connect :: Address -> Reliability -> IO (Either Error Connection)
@@ -293,7 +293,7 @@ one option might be
Receive ConnectionId [ByteString]
| ConnectionClosed ConnectionId
| ConnectionOpened ConnectionId ConnectionType Reliability Address
-{% endhighlight %}
+```
The advantage of this approach is it's consistency with the rest of the
interface. The problem is that with multicast we cannot reliably send any
@@ -308,7 +308,7 @@ multicast protocols, then that would fit this design).
If we don't want to support multiple lightweight connections to a multicast
group then a better design would be
-{% highlight haskell %}
+```haskell
data EndPoint = EndPoint {
, connect :: Address -> Reliability -> IO (Either Error Connection)
, multicastSend :: MulticastAddress -> [ByteString] -> IO ()
@@ -317,11 +317,11 @@ group then a better design would be
data Event =
...
| MulticastReceive Address [ByteString]
-{% endhighlight %}
+```
or alternatively
-{% highlight haskell %}
+```haskell
data EndPoint = EndPoint {
...
, resolveMulticastGroup :: MulticastAddress -> IO (Either Error MulticastGroup)
@@ -331,7 +331,7 @@ or alternatively
, ...
, send :: [ByteString] -> IO ()
}
-{% endhighlight %}
+```
If we do this however we need to make sure that newGroup is part an `EndPoint`,
not the `Transport`, otherwise `send` will not know the source of the message.
@@ -344,7 +344,7 @@ some point too.
The above considerations lead to the following tentative proposal:
-{% highlight haskell %}
+```haskell
data Transport = Transport {
newEndPoint :: IO (Either Error EndPoint)
}
@@ -377,7 +377,7 @@ The above considerations lead to the following tentative proposal:
, multicastUnsubscribe :: IO ()
, multicastClose :: IO ()
}
-{% endhighlight %}
+```
where `multicastClose` indicates to the runtime that this endpoint no longer
wishes to send to this multicast group, and we can therefore deallocate the
diff --git a/wiki/newdesign.md b/wiki/newdesign.md
index c1ce31d..7c3499d 100644
--- a/wiki/newdesign.md
+++ b/wiki/newdesign.md
@@ -231,52 +231,52 @@ We start with a Transport. Creating a Transport is totally backend dependent. Mo
A Transport lets us create new connections. Our current implementation provides ordinary reliable many-to-one connections, plus the multicast one-to-many connections. It does not yet provide unordered or unreliable many-to-one connections, but these will closely follow the interface for the ordinary reliable many-to-one connections.
-{% highlight haskell %}
+```haskell
data Transport = Transport
{ newConnectionWith :: Hints -> IO TargetEnd
, newMulticastWith :: Hints -> IO MulticastSourceEnd
, deserialize :: ByteString -> Maybe Address
}
-{% endhighlight %}
+```
We will start with ordinary connections and look at multicast later.
We will return later to the meaning of the hints. We have a helper function for the common case of default hints.
-{% highlight haskell %}
+```haskell
newConnection :: Transport -> IO TargetEnd
newConnection transport = newConnectionWith transport defaultHints
-{% endhighlight %}
+```
The `newConnection` action creates a new connection and gives us its `TargetEnd`. The `TargetEnd` is a stateful object representing one endpoint of the connection. For the corresponding source side, instead of creating a stateful `SourceEnd` endpoint, we can take the address of any `TargetEnd`:
-{% highlight haskell %}
+```haskell
address :: TargetEnd -> Address
-{% endhighlight %}
+```
The reason for getting the address of the target rather than `newConnection` just giving us a `SourceEnd` is that usually we only want to create a `SourceEnd` on remote nodes not on the local node.
An `Address` represents an address of an existing endpoint. It can be serialised and copied to other nodes. On the remote node the Transport's `deserialize` function is is used to reconstruct the `Address` value. Once on the remote node, a `SourceEnd` can created that points to the `TargetEnd` identified by the `Address`.
-{% highlight haskell %}
+```haskell
data Address = Address
{ connectWith :: SourceHints -> IO SourceEnd
, serialize :: ByteString
}
-{% endhighlight %}
+```
Again, ignore the hints for now.
-{% highlight haskell %}
+```haskell
connect :: Address -> IO SourceEnd
connect address = connectWith address defaultSourceHints
-{% endhighlight %}
+```
The `connect` action makes a stateful endpoint from the address. It is what really establishes a connection. After that the `SourceEnd` can be used to send messages which will be received at the `TargetEnd`.
The `SourceEnd` and `TargetEnd` are then relatively straightforward. They are both stateful endpoint objects representing corresponding ends of an established connection.
-{% highlight haskell %}
+```haskell
newtype SourceEnd = SourceEnd
{ send :: [ByteString] -> IO ()
}
@@ -285,7 +285,7 @@ newtype TargetEnd = TargetEnd
{ receive :: IO [ByteString]
, address :: Address
}
-{% endhighlight %}
+```
The `SourceEnd` sports a vectored send. That is, it allows sending a message stored in a discontiguous buffer (represented as a list of ByteString chunks). The `TargetEnd` has a vectored receive, though it is not vectored in the traditional way because it is the transport not the caller that handles the buffers and decides if it will receive the incoming message into a single contiguous buffer or a discontiguous buffer. Callers must always be prepared to handle discontiguous incoming messages or pay the cost of copying into a contiguous buffer.
@@ -306,7 +306,7 @@ For the multicast connections, the address, source and target ends are analogous
The `newMulticast` is the other way round compared to `newConnection`: it gives us a stateful `MulticastSourceEnd` from which we can obtain the address `MulticastAddress`.
-{% highlight haskell %}
+```haskell
newMulticast :: Transport -> IO MulticastSourceEnd
newMulticast transport = newMulticastWith transport defaultHints
@@ -323,7 +323,7 @@ newtype MulticastAddress = MulticastAddress
newtype MulticastTargetEnd = MulticastTargetEnd
{ multicastReceive :: IO [ByteString]
}
-{% endhighlight %}
+```
The multicast send has an implementation-defined upper bound on the message size which can be discovered on a per-connection basis.
@@ -334,17 +334,17 @@ Creating a `Transport` object is completely backend-dependent. There is the oppo
In the simplest case (e.g. a dummy in-memory transport) there might be nothing to configure:
-{% highlight haskell %}
+```haskell
mkTransport :: IO Transport
-{% endhighlight %}
+```
For a TCP backend we might have:
-{% highlight haskell %}
+```haskell
mkTransport :: TCPConfig -> IO Transport
data TCPConfig = ...
-{% endhighlight %}
+```
This `TCPConfig` can contain arbitrary amounts of configuration data. Exactly what it contains is closely connected with how we should set per-connection parameters.
@@ -359,11 +359,11 @@ With our design approach it is easy to pass backend-specific types and configura
This makes it easy to use a constant set of configuration parameters for every connection. For example for our example TCP backend above we could have:
-{% highlight haskell %}
+```haskell
data TCPConfig = TCPConfig {
socketConfiguration :: SocketOptions
}
-{% endhighlight %}
+```
This has the advantage that it gives us full access to all the options using the native types of the underlying network library (`SocketOptions` type comes from the `network` library).
@@ -371,23 +371,23 @@ The drawback of this simple approach is that we cannot set different options for
Allowing different connection options depending on the source and destination addresses is reasonably straightforward:
-{% highlight haskell %}
+```haskell
data TCPConfig = TCPConfig {
socketConfiguration :: Ip.Address -> Ip.Address
-> SocketOptions
}
-{% endhighlight %}
+```
We simply make the configuration be a function that returns the connection options but is allowed to vary depending on the IP addresses involved. Separately this could make use of configuration data such as a table of known nodes, perhaps passed in by a cluster job scheduler.
Having options vary depending on how the connection is to be used is more tricky. If we are to continue with this approach then it relies on the transport being able to identify how a client is using (or intends to use) each connection. Our proposed solution is that when each new connection is made, the client supplies a set of "hints". These are not backend specific, they are general indications of what the client wants, or how the client intends to use the connection. The backend can then interpret these hints and transform them into the real network-specific connection options:
-{% highlight haskell %}
+```haskell
data TCPConfig = TCPConfig {
socketConfiguration :: Hints -> Ip.Address -> Ip.Address
-> SocketOptions
}
-{% endhighlight %}
+```
What exactly goes into the hints will have to be considered in consultation with networking experts and people implementing backends. In particular it might indicate if bandwidth or latency is more important (e.g. to help decide if NO_DELAY should be used), if the connection is to be heavily or lightly used (to help decide buffer size) etc.
@@ -418,29 +418,29 @@ A `ProcessId` serves two purposes, one is to communicate with a process directly
The main APIs involving a ProcessId are:
-{% highlight haskell %}
+```haskell
getSelfPid :: ProcessM ProcessId
send :: Serializable a => ProcessId -> a -> ProcessM ()
spawn :: NodeId -> Closure (ProcessM ()) -> ProcessM ProcessId
-{% endhighlight %}
+```
and linking and service requests:
-{% highlight haskell %}
+```haskell
linkProcess :: ProcessId -> ProcessM ()
monitorProcess :: ProcessId -> ProcessId -> MonitorAction -> ProcessM ()
nameQuery :: NodeId -> String -> ProcessM (Maybe ProcessId)
-{% endhighlight %}
+```
A NodeId is used to enable us to talk to the service processes on a node.
The main APIs involving a `NodeId` are:
-{% highlight haskell %}
+```haskell
getSelfNode :: ProcessM NodeId
spawn :: NodeId -> Closure (ProcessM ()) -> ProcessM ProcessId
nameQuery :: NodeId -> String -> ProcessM (Maybe ProcessId)
-{% endhighlight %}
+```
### NodeID and ProcessId representation
@@ -454,15 +454,15 @@ So for a ProcessId we need:
We define it as
-{% highlight haskell %}
+```haskell
data ProcessId = ProcessId SourceEnd NodeId LocalProcessId
-{% endhighlight %}
+```
For a `NodeId` we need to be able to talk to the service processes on that node.
-{% highlight haskell %}
+```haskell
data NodeId = NodeId SourceEnd
-{% endhighlight %}
+```
The single 'SourceEnd's is for talking to the basic service processes (ie the processes involved in implementing spawn and link/monitor). The service process Ids on each node are well known and need not be stored.
@@ -470,17 +470,17 @@ The single 'SourceEnd's is for talking to the basic service processes (ie the pr
A cloud Haskell channel `SendPort` is similar to a `ProcessId` except that we do not need the `NodeId` because we do not need to talk about the process on the other end of the port.
-{% highlight haskell %}
+```haskell
data SendPort a = SendPort SourceEnd
-{% endhighlight %}
+```
### Cloud Haskell backend initialisation and neighbour setup
In the first implementation, the initialisation was done using:
-{% highlight haskell %}
+```haskell
remoteInit :: Maybe FilePath -> (String -> ProcessM ()) -> IO ()
-{% endhighlight %}
+```
This takes a configuration file (or uses an environment variable to find the same), an initial process, and it launches everything by reading the config, creating the local node and running the initial process. The initial process gets passed some role string obtained from the configuration file.
@@ -488,11 +488,11 @@ One of the slightly tricky issues with writing a program for a cluster is how to
The first cloud Haskell implementation provides:
-{% highlight haskell %}
+```haskell
type PeerInfo = Map String [NodeId]
getPeers :: ProcessM PeerInfo
findPeerByRole :: PeerInfo -> String -> [NodeId]
-{% endhighlight %}
+```
The implementation obtains this information using magic and configuration files.
@@ -511,51 +511,51 @@ So in the new design, each application selects a Cloud Haskell backend by import
Exactly how this is exposed has not been finalised. Internally we have an abstraction `LocalNode` which is a context object that knows about all the locally running processes on the node. We have:
-{% highlight haskell %}
+```haskell
newLocalNode :: Transport -> IO LocalNode
runProcess :: LocalNode -> Process () -> IO ()
-{% endhighlight %}
+```
and each backend will (at least internally) have a function something like:
-{% highlight haskell %}
+```haskell
mkTransport :: {...config...} -> IO Transport
-{% endhighlight %}
+```
So the initialisation process is more or less
-{% highlight haskell %}
+```haskell
init :: {...} -> Process () -> IO ()
init config initialProcess = do
transport <- mkTransport config
localnode <- newLocalNode transport
runProcess localnode initialProcess
-{% endhighlight %}
+```
We could export all these things and have applications plug them together.
Alternatively we might have each backend provide an initialisation that does it all in one go. For example the backend that forks multiple OS process might have an init like this:
-{% highlight haskell %}
+```haskell
init :: Int -> ([NodeId] -> Process ()) -> IO ()
-{% endhighlight %}
+```
It takes a number of (OS) processes to fork and the initial (CH) process gets passes a corresponding number of remote `NodeId`s.
For the backend that deals with VMs in the cloud, it might have two initialisation functions, one for the controller node and one for worker nodes.
-{% highlight haskell %}
+```haskell
initController :: ControllerConfig -> Process () -> IO ()
initWorker :: WorkerConfig -> IO ()
-{% endhighlight %}
+```
Additionally it might have actions for firing up new VMs and running the program binary in worker mode on that VM:
-{% highlight haskell %}
+```haskell
spawnVM :: VmAccount -> IO VM
initOnVM :: VM -> IO NodeId
shutdownVM :: VM -> IO ()
-{% endhighlight %}
+```
For example, supposing in our application's 'main' we call the IP backend and initialise a Transport object, representing the transport backend for cloud Haskell:
@@ -565,9 +565,9 @@ There are contexts where it makes sense to use more than one `Transport` in a si
There are various challenges related to addressing. Assuming these can be solved, it should be considered how initialisation might be done when there are multiple transports / backends in use. We might want to have:
-{% highlight haskell %}
+```haskell
newLocalNode :: [Transport] -> IO LocalNode
-{% endhighlight %}
+```
and expose it to the clients.
diff --git a/wiki/newtransports.md b/wiki/newtransports.md
index 69d614f..d3e0ddc 100644
--- a/wiki/newtransports.md
+++ b/wiki/newtransports.md
@@ -8,15 +8,15 @@ wiki: Guide
On this page we describe the TCP Transport as an example for developers who wish to write their own instantiations of the Transport layer. The purpose of any such instantiation is to provide a function
-{% highlight haskell %}
+```haskell
createTransport :: -> IO (Either Transport)
-{% endhighlight %}
+```
For instance, the TCP transport offers
-{% highlight haskell %}
+```haskell
createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
-{% endhighlight %}
+```
This could be the only function that `Network.Transport.TCP` exports (the only reason it exports more is to provide an API for unit tests for the TCP transport, some of which work at a lower level). Your implementation will now be guided by the `Network.Transport` API. In particular, you will need to implement `newEndPoint`, which in turn will require you to implement `receive`, `connect`, etc.
@@ -85,23 +85,23 @@ In the TCP transport `createTransport` needs to do some setup, `newEndPoint` bar
Network.Transport API functions should not throw any exceptions, but declare explicitly in their types what errors can be returned. This means that we are very explicit about which errors can occur, and moreover map Transport-specific errors ("socket unavailable") to generic Transport errors ("insufficient resources"). A typical example is `connect` with type:
-{% highlight haskell %}
+```haskell
connect :: EndPoint -- ^ Local endpoint
-> EndPointAddress -- ^ Remote endpoint
-> Reliability -- ^ Desired reliability of the connection
-> IO (Either (TransportError ConnectErrorCode) Connection)
-{% endhighlight %}
+```
`TransportError` is defined as
-{% highlight haskell %}
+```haskell
data TransportError error = TransportError error String
deriving Typeable
-{% endhighlight %}
+```
and has `Show` and `Exception` instances so that application code has the option of `throw`ing returned errors. Here is a typical example of error handling in the TCP transport; it is an internal function that does the initial part of the TCP connection setup: create a new socket, and the remote endpoint ID we're interested in and our own address, and then wait for and return the response:
-{% highlight haskell %}
+```haskell
socketToEndPoint :: EndPointAddress -- ^ Our address
-> EndPointAddress -- ^ Their address
-> IO (Either (TransportError ConnectErrorCode) (N.Socket, ConnectionRequestResponse))
@@ -128,13 +128,13 @@ socketToEndPoint (EndPointAddress ourAddress) theirAddress = try $ do
invalidAddress = TransportError ConnectNotFound . show
insufficientResources = TransportError ConnectInsufficientResources . show
failed = TransportError ConnectFailed . show
-{% endhighlight %}
+```
Note how exceptions get mapped to `TransportErrors` using `mapExceptionID`, which is defined in `Network.Transport.Internal` as
-{% highlight haskell %}
+```haskell
mapExceptionIO :: (Exception e1, Exception e2) => (e1 -> e2) -> IO a -> IO a
mapExceptionIO f p = catch p (throw . f)
-{% endhighlight %}
+```
Moreover, the original exception is used as the `String` part of the `TransportError`. This means that application developers get transport-specific feedback, which is useful for debugging, not cannot take use this transport-specific information in their _code_, which would couple applications to tightly with one specific transport implementation.