ideas-0.6: src/Common/View.hs
-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer : bastiaan.heeren@ou.nl
-- Stability : provisional
-- Portability : portable (depends on ghc)
--
-- This module defines views on data-types
--
-----------------------------------------------------------------------------
module Common.View
( -- * Generalized monadic views
ViewM, match, build, makeView, biArr, identity, (>>>)
, canonical, canonicalWith
, Control.Arrow.Arrow(..), Control.Arrow.ArrowChoice(..)
-- * Simple views
, View, ViewList, Match, belongsTo
, simplify, simplifyWith, viewEquivalent, viewEquivalentWith
, isCanonical, isCanonicalWith, matchM, canonicalM, viewList
-- * Some combinators
, listView, switchView, ( #> ), associativeView
-- * Properties on views
, propIdempotence, propSoundness, propNormalForm
) where
import Common.Traversable
import Control.Arrow hiding ((>>>))
import Control.Monad
import Data.Maybe
import Test.QuickCheck
import qualified Control.Category as C
----------------------------------------------------------------------------------
-- Generalized monadic view
-- For all v::View the following should hold:
-- 1) simplify v a "is equivalent to" a
-- 2) match (build b) equals Just b
-- (but only for b that have at least one "a")
--
-- Derived property: simplification is idempotent
data ViewM m a b = ViewM
{ match :: a -> m b
, build :: b -> a
}
makeView :: Monad m => (a -> m b) -> (b -> a) -> ViewM m a b
makeView = ViewM
biArr :: Monad m => (a -> b) -> (b -> a) -> ViewM m a b
biArr f g = makeView (return . f) g
canonical :: Monad m => ViewM m a b -> a -> m a
canonical = canonicalWith id
canonicalWith :: Monad m => (b -> b) -> ViewM m a b -> a -> m a
canonicalWith f view = liftM (build view . f) . match view
---------------------------------------------------------------
-- Arrow combinators
identity :: Monad m => ViewM m a a
identity = makeView return id
(>>>) :: Monad m => ViewM m a b -> ViewM m b c -> ViewM m a c
v >>> w = makeView (\a -> match v a >>= match w) (build v . build w)
instance Monad m => C.Category (ViewM m) where
id = identity
v . w = w >>> v
instance Monad m => Arrow (ViewM m) where
arr f = biArr f (error "Control.View.arr: function is not invertible")
first v = makeView
(\(a, c) -> match v a >>= \b -> return (b, c))
(first (build v))
second v = makeView
(\(a, b) -> match v b >>= \c -> return (a, c))
(second (build v))
v *** w = makeView
(\(a, c) -> liftM2 (,) (match v a) (match w c))
(build v *** build w)
-- left-biased builder
v &&& w = makeView
(\a -> liftM2 (,) (match v a) (match w a))
(\(b, _) -> build v b)
instance Monad m => ArrowChoice (ViewM m) where
left v = makeView
(either (liftM Left . match v) (return . Right))
(either (Left . build v) Right)
right v = makeView
(either (return . Left) (liftM Right . match v))
(either Left (Right . build v))
v +++ w = makeView
(either (liftM Left . match v) (liftM Right . match w))
(either (Left . build v) (Right . build w))
-- left-biased builder
v ||| w = makeView
(either (match v) (match w))
(Left . build v)
---------------------------------------------------------------
-- Simple views (based on a particular monad)
type View = ViewM Maybe
type ViewList = ViewM []
type Match a b = a -> Maybe b
simplify :: View a b -> a -> a
simplify = simplifyWith id
simplifyWith :: (b -> b) -> View a b -> a -> a
simplifyWith f view a = fromMaybe a (canonicalWith f view a)
belongsTo :: a -> View a b -> Bool
belongsTo a view = isJust (match view a)
viewEquivalent :: Eq b => View a b -> a -> a -> Bool
viewEquivalent = viewEquivalentWith (==)
viewEquivalentWith :: (b -> b -> Bool) -> View a b -> a -> a -> Bool
viewEquivalentWith eq view x y =
case (match view x, match view y) of
(Just a, Just b) -> a `eq` b
_ -> False
isCanonical :: Eq a => View a b -> a -> Bool
isCanonical = isCanonicalWith (==)
isCanonicalWith :: (a -> a -> Bool) -> View a b -> a -> Bool
isCanonicalWith eq v a = maybe False (eq a) (canonical v a)
-- generalized match on a ViewM Maybe
matchM :: Monad m => View a b -> a -> m b
matchM v = maybe (Prelude.fail "no match") return . match v
-- generalized canonical element on a ViewM Maybe
canonicalM :: Monad m => View a b -> a -> m a
canonicalM v = maybe (Prelude.fail "no match") return . canonicalWith id v
viewList :: Crush m => ViewM m a b -> ViewList a b
viewList v = makeView (crush . match v) (build v)
---------------------------------------------------------------
-- Some combinators
listView :: Monad m => ViewM m a b -> ViewM m [a] [b]
listView v = makeView (mapM (match v)) (map (build v))
switchView :: (Monad m, Switch f) => ViewM m a b -> ViewM m (f a) (f b)
switchView v = makeView (switch . fmap (match v)) (fmap (build v))
( #> ) :: MonadPlus m => (a -> Bool) -> ViewM m a b -> ViewM m a b
p #> v = makeView f (build v)
where f a = guard (p a) >> match v a
associativeView :: View a (a,a) -> ViewList a (a,a)
associativeView v = makeView (reverse . f) (build v)
where f a =
case matchM v a of
Just (x, y) -> [(x, y)] ++ [(x1, build v (x2, y)) | (x1, x2) <- f x]
++ [(build v (x, y1), y2) | (y1, y2) <- f y]
Nothing -> []
---------------------------------------------------------------
-- Properties on views
propIdempotence :: (Show a, Eq a) => Gen a -> View a b -> Property
propIdempotence g v = forAll g $ \a ->
let b = simplify v a
in b == simplify v b
propSoundness :: Show a => (a -> a -> Bool) -> Gen a -> View a c -> Property
propSoundness semEq g v = forAll g $ \a ->
let b = simplify v a
in semEq a b
propNormalForm :: (Show a, Eq a) => Gen a -> View a b -> Property
propNormalForm g v = forAll g $ \a -> a == simplify v a