packages feed

ideas-0.7: src/Common/Rewriting/Group.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-----------------------------------------------------------------------------
-- 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)
--
-- A hierarchy of magma's (binary operators) and groups, up to Abelian groups.
--
-----------------------------------------------------------------------------
module Common.Rewriting.Group 
   ( -- Magma
     IsMagma(..), Magma, magma, magmaView, magmaListView
   , withMatch, findMagma
   , isAssociative, isCommutative, isIdempotent
   , makeAssociative, makeCommutative, makeIdempotent
     -- Semigroup
   , IsSemiGroup(..), SemiGroup, semiGroup
   , leftIsPreferred, rightIsPreferred
   , preferLeft, preferRight
     -- Monoid
   , IsMonoid(..), Monoid, monoid
     -- Group
   , IsGroup(..), Group, group
     -- Abelian group
   , IsAbelianGroup(..), AbelianGroup, abelianGroup
   ) where

import Control.Arrow
import Common.Id
import Common.View hiding (identity)
import Common.Rewriting.Operator

-------------------------------------------------------------------
-- * Magma

class IsMagma f where 
   operation   :: f a -> a -> a -> a
   hasMagma    :: f a -> (Magma a, Magma a -> f a)
   toMagma     :: f a -> Magma a
   changeMagma :: (Magma a -> Magma a) -> f a -> f a
   -- default definitions
   operation     = binary . magmaBinaryOp . toMagma
   toMagma       = fst . hasMagma
   changeMagma f = uncurry (flip ($)) . first f . hasMagma

data Magma a = Magma
   { magmaBinaryOp   :: BinaryOp a
   , magmaProperties :: [MagmaProperty]
   }

data MagmaProperty = Associative | Commutative | Idempotent | PreferLeft
   deriving Eq

instance Show (Magma a) where
   show m = "Magma " ++ showId m

instance HasId (Magma a) where
   getId = getId . magmaBinaryOp
   changeId f m = m {magmaBinaryOp = changeId f (magmaBinaryOp m)}

instance IsMagma Magma where
   hasMagma a = (a, id)

magma :: BinaryOp a -> Magma a
magma op = Magma op []

magmaView :: IsMagma m => m a -> View a (a, a)
magmaView = binaryView . magmaBinaryOp . toMagma

-- The list can (and should) only contain more than two elements if the magma 
-- is associative
magmaListView :: IsMagma m => m a -> View a [a]
magmaListView m = makeView (Just . toList) fromList
 where
   toList = if isAssociative m then ($ []) . rec else f
 
   f a = maybe [a] (\(x, y) -> [x, y]) (match (magmaView m) a)
 
   rec a = case match (magmaView m) a of
            Just (b, c) -> rec b . rec c
            Nothing     -> (a:)

   fromList xs
      | null xs =
           error "semiGroupView.build: empty list"
      | n>2 && not (isAssociative m) =
           error $ "semiGroupView.build: not associativity for " 
                   ++ showId (toMagma m)
      | otherwise = fold (operation m) xs
    where
      n    = length xs
      fold = if hasProperty PreferLeft m then foldl1 else foldr1

withMatch :: IsMagma m => (a -> Maybe (a, a)) -> m a -> m a
withMatch f = changeMagma $ \m -> m {magmaBinaryOp = g (magmaBinaryOp m)}
 where
   g op = makeBinary (getId op) (binary op) f

isAssociative, isCommutative, isIdempotent :: IsMagma m => m a -> Bool
isAssociative = hasProperty Associative
isCommutative = hasProperty Commutative
isIdempotent  = hasProperty Idempotent

makeAssociative, makeCommutative, makeIdempotent :: IsMagma m => m a -> m a
makeAssociative = giveProperty Associative
makeCommutative = giveProperty Commutative
makeIdempotent  = giveProperty Idempotent

findMagma :: IsMagma m => (m a -> b) -> m a -> (Magma a, Magma a -> b)
findMagma f = second (f .) . hasMagma

-- helper functions
hasProperty :: IsMagma m => MagmaProperty -> m a -> Bool
hasProperty p = elem p . magmaProperties . toMagma

giveProperty :: IsMagma m => MagmaProperty -> m a -> m a
giveProperty p = changeMagma $ \m -> 
   m {magmaProperties = p:magmaProperties m}

removeProperty :: IsMagma m => MagmaProperty -> m a -> m a
removeProperty p = changeMagma $ \m -> 
   m {magmaProperties = filter (/=p) (magmaProperties m)}

-------------------------------------------------------------------
-- * SemiGroup

class IsMagma f => IsSemiGroup f where
   toSemiGroup :: f a -> SemiGroup a
   -- default definition
   toSemiGroup m = SemiGroup (rightIsPreferred m) (toMagma m)

data SemiGroup a = SemiGroup Bool (Magma a)

instance Show (SemiGroup a) where
   show m = "Semigroup " ++ showId m

instance HasId (SemiGroup a) where
   getId    = getId . toMagma
   changeId = changeMagma . changeId

instance IsMagma SemiGroup where
   hasMagma (SemiGroup b m) = findMagma (SemiGroup b) m

instance IsSemiGroup SemiGroup where
   toSemiGroup = id

semiGroup :: BinaryOp a -> SemiGroup a
semiGroup op = makeAssociative $ SemiGroup True (magma op)

leftIsPreferred, rightIsPreferred :: IsSemiGroup m => m a -> Bool
leftIsPreferred  = hasProperty PreferLeft
rightIsPreferred = not . leftIsPreferred

preferLeft, preferRight :: IsSemiGroup m => m a -> m a
preferLeft  = giveProperty PreferLeft
preferRight = removeProperty PreferLeft

-------------------------------------------------------------------
-- * Monoid

class IsSemiGroup f => IsMonoid f where
   identity    :: f a -> a
   identityCon :: f a -> Constant a
   toMonoid    :: f a -> Monoid a
   -- default definition
   identity   = constant . identityCon
   toMonoid m = Monoid (identityCon m) (toSemiGroup m)

data Monoid a = Monoid (Constant a) (SemiGroup a)

instance Show (Monoid a) where
   show m = "Monoid " ++ showId m

instance HasId (Monoid a) where
   getId    = getId . toMagma
   changeId = changeMagma . changeId

instance IsMagma Monoid where
   hasMagma (Monoid e g) = findMagma (Monoid e) g

instance IsSemiGroup Monoid

instance IsMonoid Monoid where
   identityCon (Monoid e _) = e
   toMonoid = id

monoid :: BinaryOp a -> Constant a -> Monoid a
monoid op e = Monoid e (semiGroup op)

-------------------------------------------------------------------
-- * Group

class IsMonoid f => IsGroup f where
   inverse   :: f a -> a -> a
   inverseOp :: f a -> UnaryOp a
   toGroup   :: f a -> Group a
   -- default definition
   inverse   = unary . inverseOp
   toGroup g = Group (inverseOp g) (toMonoid g)

data Group a = Group (UnaryOp a) (Monoid a)

instance Show (Group a) where
   show m = "Group " ++ showId m

instance HasId (Group a) where
   getId    = getId . toMagma
   changeId = changeMagma . changeId

instance IsMagma Group where
   hasMagma (Group inv m) = findMagma (Group inv) m

instance IsSemiGroup Group

instance IsMonoid Group where
   identityCon (Group _ m) = identityCon m

instance IsGroup Group where
   inverseOp (Group inv _) = inv
   toGroup = id

group :: BinaryOp a -> Constant a -> UnaryOp a -> Group a
group op e inv = Group inv (monoid op e)

-------------------------------------------------------------------
-- * Abelian Group

class IsGroup f => IsAbelianGroup f where
   toAbelianGroup :: f a -> AbelianGroup a
   -- default definition
   toAbelianGroup = AbelianGroup . toGroup

newtype AbelianGroup a = AbelianGroup (Group a)
   deriving (HasId, IsMagma, IsSemiGroup, IsMonoid, IsGroup)

abelianGroup :: BinaryOp a -> Constant a -> UnaryOp a -> AbelianGroup a
abelianGroup op e inv = makeCommutative $ AbelianGroup (group op e inv)

instance Show (AbelianGroup a) where
   show m = "Abelian group " ++ showId m
   
instance IsAbelianGroup AbelianGroup