{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE OverloadedStrings #-} -------------------------------------------------------------------------------- -- See end of this file for licence information. -------------------------------------------------------------------------------- -- | -- Module : LookupMap -- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012 Douglas Burke -- License : GPL V2 -- -- Maintainer : Douglas Burke -- Stability : experimental -- Portability : A lot of LANGUAGE extensions... -- -- This module defines a lookup table format and associated functions -- used by the graph matching code. -- -------------------------------------------------------------------------------- ------------------------------------------------------------ -- Generic list-of-pairs lookup functions ------------------------------------------------------------ module Data.LookupMap ( LookupEntryClass(..), LookupMap(..) , emptyLookupMap, makeLookupMap, listLookupMap , reverseLookupMap , keyOrder , mapFind, mapFindMaybe, mapContains , mapReplace, mapReplaceAll, mapReplaceMap , mapAdd, mapAddIfNew , mapDelete, mapDeleteAll , mapEq, mapKeys, mapVals , mapMerge ) where import Control.Arrow (first, second) import Data.Maybe (fromMaybe) import Data.Function (on) import Data.Ord (comparing) import Swish.Utils.ListHelpers (equiv) import qualified Data.Foldable as F import qualified Data.Traversable as T import qualified Data.List as L #if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 701) import Data.Tuple (swap) #else swap :: (a,b) -> (b,a) swap (a,b) = (b,a) #endif ------------------------------------------------------------ -- Class for lookup map entries ------------------------------------------------------------ -- |@LookupEntryClass@ defines essential functions of any datatype -- that can be used to make a 'LookupMap'. -- -- Minimal definition: @newEntry@ and @keyVal@ -- class (Eq k, Show k) => LookupEntryClass a k v | a -> k, a -> v where newEntry :: (k,v) -> a keyVal :: a -> (k,v) entryKey :: a -> k entryKey = fst . keyVal entryVal :: a -> v entryVal = snd . keyVal entryEq :: (Eq v) => a -> a -> Bool entryEq e1 e2 = keyVal e1 == keyVal e2 entryShow :: (Show v) => a -> String entryShow e = show k ++ ":" ++ show v where (k,v) = keyVal e kmap :: (LookupEntryClass a2 k2 v) => (k -> k2) -> a -> a2 kmap f = newEntry . first f . keyVal vmap :: (LookupEntryClass a2 k v2) => (v -> v2) -> a -> a2 vmap f = newEntry . second f . keyVal -- |Predefine a pair of appropriate values as a valid lookup table entry -- (i.e. an instance of LookupEntryClass). -- instance (Eq k, Show k) => LookupEntryClass (k,v) k v where newEntry = id keyVal = id -- Note: the class constraint that a is an instance of 'LookupEntryClass' -- is not defined here, for good reasons (which I forget right now, but -- something to do with the method dictionary being superfluous on -- an algebraic data type). -- |Define a lookup map based on a list of values. -- data LookupMap a = LookupMap [a] deriving (Functor, F.Foldable, T.Traversable) {- To allow this Monoid instance, we would need UndecidableInstances. Also, mapMerge can error out which is not what we would want. instance (LookupEntryClass a k v, Eq a, Show a, Ord k) => Monoid (LookupMap a) where mempty = LookupMap [] mappend = mapMerge We could use the following (perhaps with a L.nub on the result before sticling back into LookupMap) but it is unclear what the semantics are for repeated keys; it is likely to be left-biased but would leave duplicate keys in the list which could cause confusion at a later time (e.g. key removal). Many of the routines assume a single key (or single key,value) pair. instance (Eq a) => Monoid (LookupMap a) where mempty = LookupMap [] (LookupMap a) `mappend` (LookupMap b) = LookupMap (a `mappend` b)) -} gLM :: LookupMap a -> [a] gLM (LookupMap es) = es -- |Define equality of 'LookupMap' values based on equality of entries. -- -- (This is possibly a poor definition, as it is dependent on ordering -- of list members. But it passes all current test cases, and is used -- only for testing.) -- instance (Eq a) => Eq (LookupMap a) where (==) = (==) `on` gLM -- |Define Show instance for LookupMap based on Showing the -- list of entries. -- instance (Show a ) => Show (LookupMap a) where show (LookupMap es) = "LookupMap " ++ show es -- |Empty lookup map of arbitrary (i.e. polymorphic) type. -- emptyLookupMap :: (LookupEntryClass a k v) => LookupMap a emptyLookupMap = LookupMap [] -- |Function to create a `LookupMap` from a list of entries. -- makeLookupMap :: (LookupEntryClass a k v) => [a] -- ^ This list is not checked for duplicate entries, or -- entries with the same key but different values. -> LookupMap a makeLookupMap = LookupMap -- |Returns a list of lookup map entries. -- listLookupMap :: (LookupEntryClass a k v) => LookupMap a -> [a] listLookupMap = gLM -- |Given a lookup map entry, return a new entry that can be used -- in the reverse direction of lookup. This is used to construct -- a reverse LookupMap. -- reverseEntry :: (LookupEntryClass a1 k v, LookupEntryClass a2 v k) => a1 -> a2 reverseEntry = newEntry . swap . keyVal -- |Given a lookup map, return a new map that can be used -- in the opposite direction of lookup. -- reverseLookupMap :: (LookupEntryClass a1 b c, LookupEntryClass a2 c b) => LookupMap a1 -> LookupMap a2 reverseLookupMap = fmap reverseEntry -- |Given a pair of lookup entry values, return the ordering of their -- key values. -- keyOrder :: (LookupEntryClass a k v, Ord k) => a -> a -> Ordering keyOrder = comparing entryKey -- |Find key in lookup map and return corresponding value, -- otherwise return default supplied. -- mapFind :: (LookupEntryClass a k v) => v -- ^ The default value. -> k -> LookupMap a -> v mapFind def key = fromMaybe def . mapFindMaybe key -- |Find key in lookup map and return Just the corresponding value, -- otherwise return Nothing. -- mapFindMaybe :: (LookupEntryClass a k v) => k -> LookupMap a -> Maybe v mapFindMaybe key (LookupMap es) = foldr match Nothing es where match ent alt | key == entryKey ent = Just (entryVal ent) | otherwise = alt -- |Test to see if key is present in the supplied map -- mapContains :: (LookupEntryClass a k v) => LookupMap a -> k -> Bool mapContains (LookupMap es) key = any match es where match ent = key == entryKey ent -- |Replace the first occurrence of a key a with a new key-value pair, -- or add a new key-value pair if the supplied key is not already present. -- mapReplace :: (LookupEntryClass a k v) => LookupMap a -> a -> LookupMap a mapReplace (LookupMap []) newe = LookupMap [newe] mapReplace (LookupMap (e:es)) newe | entryKey e == entryKey newe = LookupMap (newe:es) | otherwise = mapAdd more e where more = mapReplace (LookupMap es) newe -- |Replace all occurrence of a key a with a new key-value pair. -- -- The resulting lookup map has the same form as the original in all -- other respects. -- mapReplaceAll :: (LookupEntryClass a k v) => LookupMap a -> a -> LookupMap a mapReplaceAll l@(LookupMap []) _ = l mapReplaceAll (LookupMap (e:es)) newe = mapAdd more e' where more = mapReplaceAll (LookupMap es) newe e' = if entryKey e == entryKey newe then newe else e -- |Replace any occurrence of a key in the first argument with a -- corresponding key-value pair from the second argument, if present. -- -- This could be implemented by multiple applications of 'mapReplaceAll', -- but is arranged differently so that only one new @LookupMap@ value is -- created. -- -- Note: keys in the new map that are not present in the old map -- are not included in the result map -- mapReplaceMap :: (LookupEntryClass a k v) => LookupMap a -> LookupMap a -> LookupMap a mapReplaceMap l@(LookupMap []) _ = l mapReplaceMap (LookupMap (e:es)) newmap = mapAdd more e' where more = mapReplaceMap (LookupMap es) newmap e' = newEntry (k, mapFind v k newmap) (k,v) = keyVal e -- |Add supplied key-value pair to the lookup map. -- -- This is effectively an optimized case of 'mapReplace' or 'mapAddIfNew', -- where the caller guarantees to avoid duplicate key values. -- mapAdd :: LookupMap a -> a -> LookupMap a mapAdd (LookupMap es) e = LookupMap (e:es) -- |Add supplied key-value pair to the lookup map, -- only if the key value is not already present. -- mapAddIfNew :: (LookupEntryClass a k v) => LookupMap a -> a -> LookupMap a mapAddIfNew emap e = if mapContains emap (entryKey e) then emap else mapAdd emap e -- |Delete the first occurrence of the key from the lookup map. -- -- If the key does not exist in the map then no change is made. -- mapDelete :: (LookupEntryClass a k v) => LookupMap a -> k -> LookupMap a mapDelete l@(LookupMap []) _ = l mapDelete (LookupMap (e:es)) k | k == entryKey e = LookupMap es | otherwise = mapAdd more e where more = mapDelete (LookupMap es) k -- |Delete all occurrences of the key from the lookup map. -- mapDeleteAll :: (LookupEntryClass a k v) => LookupMap a -> k -> LookupMap a mapDeleteAll l@(LookupMap []) _ = l mapDeleteAll (LookupMap (e:es)) k = let more = mapDeleteAll (LookupMap es) k in if entryKey e == k then more else mapAdd more e -- |Compare two lookup maps for equality. -- -- Two maps are equal if they have the same set of keys, and if -- each key maps to an equivalent value. This is only guaranteed -- if the maps do not contain duplicate entries. -- mapEq :: (LookupEntryClass a k v, Eq v) => LookupMap a -> LookupMap a -> Bool mapEq es1 es2 = ks1 `equiv` ks2 && and [ mapFindMaybe k es1 == mapFindMaybe k es2 | k <- ks1 ] where ks1 = mapKeys es1 ks2 = mapKeys es2 -- |Return the list of distinct keys in a supplied LookupMap -- mapKeys :: (LookupEntryClass a k v) => LookupMap a -> [k] mapKeys = L.nub . gLM . fmap entryKey -- |Return list of distinct values in a supplied LookupMap -- mapVals :: (Eq v, LookupEntryClass a k v) => LookupMap a -> [v] mapVals = L.nub . gLM . fmap entryVal -- |Merge two lookup maps, ensuring that if the same key appears -- in both maps it is associated with the same value. -- mapMerge :: (LookupEntryClass a k v, Eq a, Show a, Ord k) => LookupMap a -> LookupMap a -> LookupMap a mapMerge a b = LookupMap $ on merge (L.sortBy keyOrder . gLM) a b where merge es1 [] = es1 merge [] es2 = es2 merge es1@(e1:et1) es2@(e2:et2) = case keyOrder e1 e2 of LT -> e1 : merge et1 es2 GT -> e2 : merge es1 et2 EQ -> if e1 /= e2 then error ("mapMerge key conflict: " ++ show e1 ++ " with " ++ show e2) else e1 : merge et1 et2 -------------------------------------------------------------------------------- -- -- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, -- 2011, 2012 Douglas Burke -- All rights reserved. -- -- This file is part of Swish. -- -- Swish is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2 of the License, or -- (at your option) any later version. -- -- Swish is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with Swish; if not, write to: -- The Free Software Foundation, Inc., -- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -- --------------------------------------------------------------------------------