Map

Map

Map

Data

Map String Int

gfoldl

[(String,Int)]

Map

Int

Map String Int

Int

Map

Map

Int

Int

Map String Int

Map

Data.Generics.Uniplate.Data.Instances

Bool

Map

Map

Int

Map

String

Char

Map



$ import qualified Data.Map as Map

$ import Data.Char

$ import Data.Generics.Uniplate.Data

$ import Data.Generics.Uniplate.Data.Instances

$ fromMap $ transformBi toUpper $ toMap $ Map.fromList [("haskell",12),("test",18)]

fromList [("HASKELL",12),("TEST",18)]



Map

plateProject

Data.Generics.Uniplate.Data.Instances

Data

Hide

Trigger

Invariant

Map

Set

IntMap

IntSet



instance Biplate (Map.Map [Char] Int) Char where

biplate = plateProject Map.toList Map.fromList



fromDistictAscList

Map



instance Biplate (Map.Map [Char] Int) Int where

biplate = plateProject Map.toAscList Map.fromDistinctAscList



Hide



data Hide a = Hide {fromHide :: a}



Data

EmptyDataDecls



data Hide a





transformBi (+1) (1, 2, Hide 3, Just 4) == (2, 3, Hide 3, Just 5)



toConstr

gunfold

Trigger

Data



data Trigger a = Trigger {trigger :: Bool, fromTrigger :: a}



Data



data Trigger a = Trigger a



gfoldl

gunfold

trigger

True



data SortedList a = SortedList (Trigger [a]) deriving (Data,Typeable)

toSortedList xs = SortedList $ Trigger False $ sort xs

fromSortedList (SortedList (Trigger t xs)) = if t then sort xs else xs



gmapT

Trigger

Trigger

Invariant

Invariant



data Invariant a = Invariant {invariant :: a -> a, fromInvariant :: a}



Data



data Invariant a = Invariant a



gfoldl

invariant



data SortedList a = SortedList (Invariant [a]) deriving (Data,Typeable)

toSortedList xs = SortedList $ Invariant sort (sort xs)

fromSortedList (SortedList (Invariant _ xs)) = xs



gmapT

invariant

fromSortedList

gunfold

fromConstrB

fromConstr

gunfold

Hide

Trigger

Invariant

Map



newtype Map k v = Map (Invariant (Trigger [k], Trigger [v], Hide (Map.Map k v)))

deriving (Data, Typeable)



Map

Invariant

Map

Invariant

Trigger

Map

Map



fromMap (Map (Invariant _ (_,_,Hide x))) = x



Map



toMap :: Ord k => Map.Map k v -> Map k v

toMap x = Map $ Invariant inv $ create x

where

create x = (Trigger False ks, Trigger False vs, Hide x)

where (ks,vs) = unzip $ Map.toAscList x



inv (ks,vs,x)

| trigger ks = create $ Map.fromList $ zip (fromTrigger ks) (fromTrigger vs)

| trigger vs = create $ Map.fromDistinctAscList $ zip (fromTrigger ks) (fromTrigger vs)

| otherwise = (ks,vs,x)



create

Map

inv

Map

fromList

fromDistinctAscList

Map

Map

Abstract data types, such asfrom the containers package, hide their internal structure so they can maintain invariants necessary for their correct operation. Generic programming, such as the Data class used by SYB, allows programs to generically traverse data types, without detailed knowledge of their structure. The two are somewhat incompatible - ifallows manipulating it's internal structure, the invariants could be broken causingto perform incorrectly.Using theclass, aclaims it has no constructors, but if you operate on it withis behaves as though it were. When Uniplate traverses the, such as when searching for an, it first analyses the available constructors to see if acan possibly contain an. Sincehas no constructors, it concludes thatcannot contain an, and Uniplate fails to operate on the contained's.For people who use the Data-style Uniplate module, using the new version of Uniplate you can now correctly operate over, provided you use the newtypewrapper from. When you transform over(which does not touch the) it will ignore theand take. When you transform overit will reconstruct thein, and if you transformorit will reconstruct thein. Regardless of what operations you do, it will work efficiently and correctly. As an example:There are two approaches for dealing with theproblem in Uniplate. For users of the Direct-style Uniplate module, there is a function, which has been available for some time. For users of the Data-style Uniplate module, or for users of the SYB package, there is a new modulein uniplate-1.6.4 (released today) which provides three types with specialinstances (). Using these three types we can construct wrappers providing Data instances for abstract types, and Uniplate includes wrappers for several of the types in the containers package ().The plateProject function helps you define Direct Uniplate instances for abstract types. Instead of defining how to examine the data type, you instead define how to transform the data type into one you can examine, and how to transform it back. As an example:If the types ensure that any operations will not change the keys we can optimise and use thefunction to reconstruct theThedata type is useful for wrapping values that you want to ignore with Uniplate. The type is defined as:But has ainstance that pretends it is defined using the extensionas:As an example of avoiding particular values, you can write:As a result of having no constructors, any calls to the methodsorwill raise an error.Thedata type is useful for determining when a value was constructed with themethods. It is defined as:But theinstance pretends that it is defined as:However, wheneverorconstructs a new value, it will have thefield set to. The trigger information is useful to indicate whether any invariants have been broken, and thus need fixing. As an example:This data type represents a sorted list. When constructed the items are initially sorted, but operations such ascould break that invariant. Thetype is used to detect when the Data operations have been performed, and resort the list.Thetype is often used in conjunction with, which fixes the invariants.Thedata type is useful for ensuring that an invariant is always applied to a data type. It is defined as:But theinstance pretends that it is defined as:Whenever aconstructs a new value, it will have the function in thefield applied to it. As an example:Any time an operation such asis applied to the data type, thefunction is applied to the result. Thefunction can then rely on this invariant.Themethod is partially implemented - all constructed values will have an undefined value for all fields, regardless of which function is passed to. If you only use(as Uniplate does) then themethod is sufficient.Using theandtypes, we can define a wrapper for the containerstype as:Thetype is defined as anof three components - the keys, the values, and the underlying. We useto ensure that the keys/values/map always remain in sync. We useon the keys and values to ensure that whenever the keys or values change we rebuild the, but if they don't, we reuse the previous value. The function to extract a containersfrom the wrapper requires only simple pattern matching:The function to wrap a containersis slightly harder, as we need to come up with an invariant restoring function:Thefunction creates a value from a, getting the correct keys and values. Thefunction looks at the triggers on the keys/values. If the keys trigger has been tripped, then we reconstruct theusing. If the values trigger has been tripped, but they keys trigger has not, we can use, reducing the complexity of constructing the. If nothing has changed we can reuse the previous value.The end result is that all Uniplate (or SYB) traversals overresult in a valid value, which has had all appropriate transformations applied.