Boilerplate revisited

Markus.Schnell at infineon.com Markus.Schnell at infineon.com
Tue Nov 4 15:24:45 EST 2003


Perhaps some remember the question I posted about 
avoiding boilerplate some time ago: 
(http://haskell.org/pipermail/haskell/2003-August/012479.html)

After some experimentation I employed Hal's DynamicMap as well
as the Generics library. This worked fine for a while, but after
I started using my program on bigger problems it soon turned out
that the program spent more than 40% (!) of its memory on the
lookupDM operation and most of its time on stypeOf.

This was not acceptable and so I switched back to my original 
solution (albeit somewhat easier implemented), which is...
...using lots of boilerplate. The memory usage drops to 
roughly 2% for lookupDM.

For those interested, the approach is described below.
Instead of coding the boilerplate by hand, I wrote a
function for automatic generation, making it more robust 
and extensible.
There's also room for improvement, but I needed a quick,
working solution, not a perfect one.

The alert reader may have noticed that I didn't mention
Generics again. First I was using a map function like this

> segmap f s = everywhere (mkT f) s

planning to explore some of the visiting strategies.

But this resulted in spending a lot of time in the segmap function, 
while using a self-written map on the structure was much faster. 
I didn't keep records, though.
Perhaps the boilerplate people (Ralf Lämmel, Simon PJ) have already
looked at performance issues?


If you stayed with me, thank you for your time.

Markus





 === Approach Description ===

Four modules are of interest:
Defs        -  attribute definitions
Attribute   -  setter/getter/helper functions
MyTypeable  -  boilerplate
DynamicMap  -  an interface to finite map


Here are the prototypical implementations:


module Defs
where

data Gender    = Masc | Fem | Neutr    deriving (Eq, Show)
data Accented  = Accented  deriving (Eq, Show)
data Focused   = Focused   deriving (Eq, Show)

...






module Attribute
  ( Gender
  , gender,  accented, focused
  , ggender, isAccented, isFocused ) where

import Defs
import MyTypeable
import DynamicMap
...

-- Segment is the manipulated structure, containing the attributes
sattr :: (MyTypeable a) => a -> Segment -> Segment
sattr a s =  s { attrs = sattrW a (attrs s) }

sattrW :: (MyTypeable a) => a -> Attrs -> Attrs
sattrW x  = (flip addToDM) x 

gattr :: (MyTypeable a) => Segment -> Maybe a
gattr = gattrW . attrs 

gattrW :: (MyTypeable a) => Attrs -> Maybe a                   
gattrW = lookupDM

type GetAttr a = Segment -> Maybe a
type SetAttr a = a -> Segment -> Segment

ggender    = gattr :: GetAttr Gender
gender     = sattr :: SetAttr Gender

accented   = sattr Accented
focused    = sattr Focused

isAccented, isFocused :: Segment -> Bool
isAccented = isJust . (gattr::GetAttr Accented)
isFocused  = isJust . (gattr::GetAttr Focused)

...






module DynamicMap ( ... ) where -- shamelessly building on Hal Daumé's
module

import Data.FiniteMap
import MyTypeable

newtype DynamicMap = DM { unDM :: FiniteMap TypeRep TypeWrapper }

-- ignoring some obvious stuff (emptyDM, ...)


addToDM :: (MyTypeable a) => DynamicMap -> a -> DynamicMap
addToDM (DM dm) a = DM $ addToFM dm (typeOf a) (toDyn a)

lookupDM :: MyTypeable a => DynamicMap -> Maybe a
lookupDM (DM dm) :: Maybe a =
    case lookupFM dm (typeOf (undefined :: a)) of
      Nothing -> Nothing
      Just x  -> fromDyn x








module MyTypeable where

import Defs

type TypeRep = Int

class MyTypeable a where
  typeOf  :: a -> TypeRep
  toDyn   :: a -> TypeWrapper
  fromDyn :: TypeWrapper -> Maybe a



-- === Start Of Automatic Generated Code
=======================================
-- put in here automatic generated code
-- === End Of Automatic Generated Code
========================================

-- The following generates the wrappers for the types. This is a very
-- straightforward implementation, no need for optimization encountered.
-- Call gentmp and put it in the "automatic generated code" section.

gentmp = writeFile "tmp.txt" (generateTypeWrappers typesToWrap)

typesToWrap = [ "Gender", "Accented", "Focused" ]

generateTypeWrappers = generateTypeWrappers' "" "" 0 

generateTypeWrappers'
  :: String   -- TypeWrapper accu
  -> String   -- Instances accu
  -> Int      -- current instance
  -> [String] -- list of types
  -> String   -- code string

generateTypeWrappers' tpa ia n [] 
  = "data TypeWrapper = StandInForEasierAutomaticGeneration\n"
    ++ tpa ++ "\n\n" ++ ia

generateTypeWrappers' tpa ia n (x:xs)
  = generateTypeWrappers' (tpa ++ wrap n x) (ia ++ inst n x) (n + 1) xs
  

-- make one wrap
wrap n x = "  | T" ++ show n ++ " " ++ x ++ "\n"


-- make one instance
inst n x = "instance MyTypeable " ++ x ++ " where\n"
           ++ "  typeOf _       = " ++ show n ++ "\n"
           ++ "  toDyn          = T" ++ show n ++ "\n"
           ++ "  fromDyn (T" ++ show n ++ " x) = Just x\n"
           ++ "  fromDyn _      = Nothing\n\n\n"


More information about the Haskell mailing list