diff options
Diffstat (limited to 'testsuite/tests/arrows/should_run/arrowrun002.hs')
-rw-r--r-- | testsuite/tests/arrows/should_run/arrowrun002.hs | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/testsuite/tests/arrows/should_run/arrowrun002.hs b/testsuite/tests/arrows/should_run/arrowrun002.hs new file mode 100644 index 0000000000..16f29806ac --- /dev/null +++ b/testsuite/tests/arrows/should_run/arrowrun002.hs @@ -0,0 +1,225 @@ +{-# LANGUAGE Arrows #-} + +-- Homogeneous (or depth-preserving) functions over perfectly balanced trees. + +module Main where + +import Control.Arrow +import Control.Category +import Data.Complex +import Prelude hiding (id, (.)) + +infixr 4 :&: + +-- Consider the following non-regular type of perfectly balanced trees, +-- or `powertrees' (cf Jayadev Misra's powerlists): + +data Pow a = Zero a | Succ (Pow (Pair a)) + deriving Show + +type Pair a = (a, a) + +-- Here are some example elements: + +tree0 = Zero 1 +tree1 = Succ (Zero (1, 2)) +tree2 = Succ (Succ (Zero ((1, 2), (3, 4)))) +tree3 = Succ (Succ (Succ (Zero (((1, 2), (3, 4)), ((5, 6), (7, 8)))))) + +-- The elements of this type have a string of constructors expressing +-- a depth n as a Peano numeral, enclosing a nested pair tree of 2^n +-- elements. The type definition ensures that all elements of this type +-- are perfectly balanced binary trees of this form. (Such things arise +-- in circuit design, eg Ruby, and descriptions of parallel algorithms.) +-- And the type system will ensure that all legal programs preserve +-- this structural invariant. +-- +-- The only problem is that the type constraint is too restrictive, rejecting +-- many of the standard operations on these trees. Typically you want to +-- split a tree into two subtrees, do some processing on the subtrees and +-- combine the results. But the type system cannot discover that the two +-- results are of the same depth (and thus combinable). We need a type +-- that says a function preserves depth. Here it is: + +data Hom a b = (a -> b) :&: Hom (Pair a) (Pair b) + +-- A homogeneous (or depth-preserving) function is an infinite sequence of +-- functions of type Pair^n a -> Pair^n b, one for each depth n. We can +-- apply a homogeneous function to a powertree by selecting the function +-- for the required depth: + +apply :: Hom a b -> Pow a -> Pow b +apply (f :&: fs) (Zero x) = Zero (f x) +apply (f :&: fs) (Succ t) = Succ (apply fs t) + +-- Having defined apply, we can forget about powertrees and do all our +-- programming with Hom's. Firstly, Hom is an arrow: + +instance Category Hom where + id = id :&: id + (f :&: fs) . (g :&: gs) = (f . g) :&: (fs . gs) + +instance Arrow Hom where + arr f = f :&: arr (f *** f) + first (f :&: fs) = + first f :&: (arr transpose >>> first fs >>> arr transpose) + +transpose :: ((a,b), (c,d)) -> ((a,c), (b,d)) +transpose ((a,b), (c,d)) = ((a,c), (b,d)) + +-- arr maps f over the leaves of a powertree. + +-- The composition >>> composes sequences of functions pairwise. +-- +-- The *** operator unriffles a powertree of pairs into a pair of powertrees, +-- applies the appropriate function to each and riffles the results. +-- It defines a categorical product for this arrow category. + +-- When describing algorithms, one often provides a pure function for the +-- base case (trees of one element) and a (usually recursive) expression +-- for trees of pairs. + +-- For example, a common divide-and-conquer pattern is the butterfly, where +-- one recursive call processes the odd-numbered elements and the other +-- processes the even ones (cf Geraint Jones and Mary Sheeran's Ruby papers): + +butterfly :: (Pair a -> Pair a) -> Hom a a +butterfly f = id :&: proc (x, y) -> do + x' <- butterfly f -< x + y' <- butterfly f -< y + returnA -< f (x', y') + +-- The recursive calls operate on halves of the original tree, so the +-- recursion is well-defined. + +-- Some examples of butterflies: + +rev :: Hom a a +rev = butterfly swap + where swap (x, y) = (y, x) + +unriffle :: Hom (Pair a) (Pair a) +unriffle = butterfly transpose + +-- Batcher's sorter for bitonic sequences: + +bisort :: Ord a => Hom a a +bisort = butterfly cmp + where cmp (x, y) = (min x y, max x y) + +-- This can be used (with rev) as the merge phase of a merge sort. +-- +sort :: Ord a => Hom a a +sort = id :&: proc (x, y) -> do + x' <- sort -< x + y' <- sort -< y + yr <- rev -< y' + p <- unriffle -< (x', yr) + bisort2 -< p + where _ :&: bisort2 = bisort + +-- Here is the scan operation, using the algorithm of Ladner and Fischer: + +scan :: (a -> a -> a) -> a -> Hom a a +scan op b = id :&: proc (x, y) -> do + y' <- scan op b -< op x y + l <- rsh b -< y' + returnA -< (op l x, y') + +-- The auxiliary function rsh b shifts each element in the tree one place to +-- the right, placing b in the now-vacant leftmost position, and discarding +-- the old rightmost element: + +rsh :: a -> Hom a a +rsh b = const b :&: proc (x, y) -> do + w <- rsh b -< y + returnA -< (w, x) + +-- Finally, here is the Fast Fourier Transform: + +type C = Complex Double + +fft :: Hom C C +fft = id :&: proc (x, y) -> do + x' <- fft -< x + y' <- fft -< y + r <- roots (-1) -< () + let z = r*y' + unriffle -< (x' + z, x' - z) + +-- The auxiliary function roots r (where r is typically a root of unity) +-- populates a tree of size n (necessarily a power of 2) with the values +-- 1, w, w^2, ..., w^(n-1), where w^n = r. + +roots :: C -> Hom () C +roots r = const 1 :&: proc _ -> do + x <- roots r' -< () + unriffle -< (x, x*r') + where r' = if imagPart s >= 0 then -s else s + s = sqrt r + +-- Miscellaneous functions: + +rrot :: Hom a a +rrot = id :&: proc (x, y) -> do + w <- rrot -< y + returnA -< (w, x) + +ilv :: Hom a a -> Hom (Pair a) (Pair a) +ilv f = proc (x, y) -> do + x' <- f -< x + y' <- f -< y + returnA -< (x', y') + +scan' :: (a -> a -> a) -> a -> Hom a a +scan' op b = proc x -> do + l <- rsh b -< x + (id :&: ilv (scan' op b)) -< op l x + +riffle :: Hom (Pair a) (Pair a) +riffle = id :&: proc ((x1, y1), (x2, y2)) -> do + x <- riffle -< (x1, x2) + y <- riffle -< (y1, y2) + returnA -< (x, y) + +invert :: Hom a a +invert = id :&: proc (x, y) -> do + x' <- invert -< x + y' <- invert -< y + unriffle -< (x', y') + +carryLookaheadAdder :: Hom (Bool, Bool) Bool +carryLookaheadAdder = proc (x, y) -> do + carryOut <- rsh (Just False) -< + if x == y then Just x else Nothing + Just carryIn <- scan plusMaybe Nothing -< carryOut + returnA -< x `xor` y `xor` carryIn + where plusMaybe x Nothing = x + plusMaybe x (Just y) = Just y + False `xor` b = b + True `xor` b = not b + +-- Global conditional for SIMD + +ifAll :: Hom a b -> Hom a b -> Hom (a, Bool) b +ifAll fs gs = ifAllAux snd (arr fst >>> fs) (arr fst >>> gs) + where ifAllAux :: (a -> Bool) -> Hom a b -> Hom a b -> Hom a b + ifAllAux p (f :&: fs) (g :&: gs) = + liftIf p f g :&: ifAllAux (liftAnd p) fs gs + liftIf p f g x = if p x then f x else g x + liftAnd p (x, y) = p x && p y + +maybeAll :: Hom a c -> Hom (a, b) c -> Hom (a, Maybe b) c +maybeAll (n :&: ns) (j :&: js) = + choose :&: (arr dist >>> maybeAll ns (arr transpose >>> js)) + where choose (a, Nothing) = n a + choose (a, Just b) = j (a, b) + dist ((a1, b1), (a2, b2)) = ((a1, a2), zipMaybe b1 b2) + zipMaybe (Just x) (Just y) = Just (x, y) + zipMaybe _ _ = Nothing + +main = do + print (apply rev tree3) + print (apply invert tree3) + print (apply (invert >>> sort) tree3) + print (apply (scan (+) 0) tree3) |