chp-plus-1.0.0: Control/Concurrent/CHP/Connect/TwoDim.hs
-- Communicating Haskell Processes.
-- Copyright (c) 2008--2009, University of Kent.
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
-- * Neither the name of the University of Kent nor the names of its
-- contributors may be used to endorse or promote products derived from
-- this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- | This module contains helper functions for wiring up collections of processes
-- into a two-dimensional arrangement.
module Control.Concurrent.CHP.Connect.TwoDim
(FourWay(..), wrappedGridFour, wrappedGridFour_,
FourWayDiag(..), EightWay, wrappedGridEight, wrappedGridEight_) where
import Control.Arrow
import Control.Concurrent.CHP
import Control.Concurrent.CHP.Connect
import Control.Monad
import Data.List
import Prelude hiding (abs)
-- | A data type representing four-way connectivity for a process, with channels
-- to the left and right, above and below.
data FourWay above below left right
= FourWay { above :: above, below :: below, left :: left, right :: right }
deriving (Eq)
-- | A data type representing four-way diagonal connectivity for a process, with
-- channels above-left, below-right, above-right and below-left.
data FourWayDiag aboveLeft belowRight aboveRight belowLeft
= FourWayDiag { aboveLeft :: aboveLeft, belowRight :: belowRight, aboveRight :: aboveRight, belowLeft :: belowLeft }
deriving (Eq)
-- | EightWay is simply a synonym for a pair of 'FourWay' and 'FourWayDiag'.
type EightWay a b l r al br ar bl = (FourWay a b l r, FourWayDiag al br ar bl)
-- | Wires the given grid of processes (that require four-way connectivity) together
-- into a wrapped around grid (a torus) and runs them all in parallel.
--
-- The parameter is a list of rows, and should be rectangular (i.e. all the rows
-- should be the same length). If not, an error will result. The return value
-- is guaranteed to be the same shape as the input.
--
-- It is worth remembering that if you have only one row or one column (or
-- both), processes can be connected to themselves, so make sure that if a
-- process is connected to itself (e.g. its left channel connects to its right
-- channel), it is coded such that it won't deadlock -- or if needed, checks for this
-- possibility using 'sameChannel'. Processes may also be connected to each other
-- multiple times -- in a two-wide grid, each process's left channel connects to
-- the same process as its right.
wrappedGridFour :: (Connectable above below, Connectable left right) =>
[[FourWay above below left right -> CHP a]] -> CHP [[a]]
wrappedGridFour ps
-- If ps == [], this will succeed, and map connectRowCycle ps will be [],
-- and thus connectColumnsCycle _ [] will return [] (without forcing the
-- head call), and it will all work correctly.
| length (nub $ map length ps) <= 1
= connectColumnsCycle (length (head ps)) $ map connectRowCycle ps
| otherwise
= error $ "Control.Concurrent.CHP.Connect.TwoDim.wrappedGrid: Non-rectangular input "
++ " height: " ++ show (length ps) ++ " widths: " ++ show (map length ps)
-- | Like 'wrappedGridFour' but discards the return values.
wrappedGridFour_ :: (Connectable above below, Connectable left right) =>
[[FourWay above below left right -> CHP a]] -> CHP ()
wrappedGridFour_ ps = wrappedGridFour ps >> return () --TODO fix this
-- | Like 'wrappedGridFour' but provides eight-way connectivity.
--
-- The note on 'wrappedGridFour' about processes being connected to themselves
-- applies here too -- as does the note about processes being connected to
-- each other multiple times. If you have one row, a process's left,
-- above-left and below-left channels all connect to the same process. If you
-- have a two-by-two grid, a process's four diagonal channels all connect to
-- the same process.
wrappedGridEight :: (Connectable above below, Connectable left right,
Connectable aboveLeft belowRight, Connectable belowLeft aboveRight) =>
[[EightWay above below left right aboveLeft belowRight aboveRight belowLeft -> CHP a]] -> CHP [[a]]
wrappedGridEight ps
| length (nub $ map length ps) <= 1
= connectColumnsCycleDiag (length (head ps)) $ map connectRowCycleDiag ps
| otherwise
= error $ "Control.Concurrent.CHP.Connect.TwoDim.wrappedGridDiag: Non-rectangular input "
++ " height: " ++ show (length ps) ++ " widths: " ++ show (map length ps)
-- | Like 'wrappedGridEight' but discards the output.
wrappedGridEight_ :: (Connectable above below, Connectable left right,
Connectable aboveLeft belowRight, Connectable belowLeft aboveRight) =>
[[EightWay above below left right aboveLeft belowRight aboveRight belowLeft -> CHP a]] -> CHP ()
wrappedGridEight_ ps = wrappedGridEight ps >> return ()
connectRowCycle :: Connectable left right =>
[FourWay above below left right -> CHP a] -> ([(above, below)] -> CHP [a])
connectRowCycle [] _ = return []
connectRowCycle allps abs = connect $
foldr connLR
-- The last process is special because it must take both channels for itself:
(liftM (:[]) . last allps . uncurry (uncurry FourWay $ last abs))
(zip (init allps) (init abs))
connLR :: Connectable left right =>
(FourWay above below left right -> CHP a, (above, below))
-> ((left, right) -> CHP [a])
-> ((left, right) -> CHP [a])
connLR (p, (a, b)) q (l, r)
= liftM (uncurry (:)) . connect $ \(l', r') -> p (FourWay a b l r') <||> q (l', r)
connectColumnsCycle :: Connectable above below => Int -> [[(above, below)] -> CHP [a]] -> CHP [[a]]
connectColumnsCycle _ [] = return []
connectColumnsCycle n ps = connectList n $ foldl1 (connAB n) (map (liftM (:[]) .) ps)
connAB :: Connectable above below => Int -> ([(above, below)] -> CHP [a]) -> ([(above, below)] -> CHP [a]) -> ([(above, below)] -> CHP [a])
connAB n p q abs = liftM (uncurry (++)) $ connectList n $ \abs' -> p (zip (map fst abs) (map snd abs'))
<||> q (zip (map fst abs') (map snd abs))
connectColumnsCycleDiag :: (Connectable a b, Connectable bl ar, Connectable al br) =>
Int -> [[((a, b), FourWayDiag al br ar bl)] -> CHP [z]] -> CHP [[z]]
connectColumnsCycleDiag _ [] = return []
connectColumnsCycleDiag n ps = connectList n $ \abs ->
connectList n $ \leadingDiag -> connectList n $ \otherDiag ->
foldl1 (connABDiag n) (map (liftM (:[]) .) ps)
$ zip abs [FourWayDiag al br ar bl
| (_, ar) <- otherDiag
| (bl, _) <- shiftRight otherDiag
| (al, _) <- leadingDiag
| (_, br) <- shiftLeft leadingDiag]
-- Let's imagine we have a square:
--
-- A B C
-- D E F
-- G H I
--
-- We pass in the outer-most channels as the processes need them to be wired.
--
-- So for example, A will recieve:
-- aboveLeft: AI
-- aboveRight AH
-- belowLeft: AF
-- belowRight: AE
--
-- So for example when we create the leadingDiag channels:
--
-- \1 \2 \3
-- A B C
--
-- The ends are passed to the above channels as-is, but to the below channels shifted lleft:
--
-- G H I
-- \2 \3 \1
--
-- For the otherDiag, shifted right when below:
--
-- /1 /2 /3
-- A B C
--
-- G H I
-- /3 /1 /2
shiftLeft, shiftRight :: [a] -> [a]
shiftLeft [] = []
shiftLeft xs = tail xs ++ [head xs]
shiftRight [] = []
shiftRight xs = last xs : init xs
connABDiag :: (Connectable above below, Connectable al br, Connectable bl ar) =>
Int -> ([((above, below), FourWayDiag al br ar bl)] -> CHP [a])
-> ([((above, below), FourWayDiag al br ar bl)] -> CHP [a])
-> ([((above, below), FourWayDiag al br ar bl)] -> CHP [a])
connABDiag n p q abs = liftM (uncurry (++)) $ connectList n $ \abs' ->
connectList n $ \leadingDiag -> connectList n $ \otherDiag ->
p [((a, b), FourWayDiag al br ar bl)
| ((a, _), _) <- abs
| (_, b) <- abs'
| (_, FourWayDiag al _ ar _) <- abs
| (bl, _) <- shiftRight otherDiag
| (_, br) <- shiftLeft leadingDiag
]
<||>
q [((a, b), FourWayDiag al br ar bl)
| ((_, b), _) <- abs
| (a, _) <- abs'
| (al, _) <- leadingDiag
| (_, ar) <- otherDiag
| (_, FourWayDiag _ br _ bl) <- abs
]
-- We are given our own above and below as we need them to be arranged already.
connectRowCycleDiag :: Connectable l r =>
[EightWay a b l r al br ar bl -> CHP z]
-> ([((a, b), FourWayDiag al br ar bl)] -> CHP [z])
connectRowCycleDiag [] _ = return []
connectRowCycleDiag allps abs = connect $
foldr connLRDiag
-- The last process is special because it must take both channels for itself:
(\lr -> liftM (:[]) $ last allps $ first (($ lr) . uncurry . uncurry FourWay) (last abs))
(zip (init allps) (init abs))
connLRDiag :: Connectable l r =>
(EightWay a b l r al br ar bl -> CHP z, ((a, b), FourWayDiag al br ar bl))
-> ((l, r) -> CHP [z])
-> ((l, r) -> CHP [z])
connLRDiag (p, ((a, b), diag)) q (l, r)
= liftM (uncurry (:)) . connect $ \(l', r') -> p (FourWay a b l r', diag) <||> q (l', r)