baskell 0.1 → 0.1.1
raw patch · 3 files changed
+787/−3 lines, 3 files
Files
- baskell.cabal +5/−3
- src/TypeCheck.hs +741/−0
- src/Utils.hs +41/−0
baskell.cabal view
@@ -1,5 +1,5 @@ Name: baskell-Version: 0.1+Version: 0.1.1 Copyright: 2004-2005, Bernard Pope License: GPL License-File: LICENSE@@ -47,7 +47,9 @@ Parser, Pretty, Reduce,- Type-ghc-options: -O2 -Wall -optl-Wl,-s+ Type,+ TypeCheck,+ Utils+ghc-options: -O2 -Wall ghc-prof-options: -prof -auto-all
+ src/TypeCheck.hs view
@@ -0,0 +1,741 @@+{-# LANGUAGE CPP #-}+{-------------------------------------------------------------------------------++ Copyright: Bernie Pope 2004++ Module: TypeCheck++ Description: Infer types for Baskell programs and+ expressions. Type inference is based on+ a simple constraint solving process.++ A single pass is made over the AST to+ generate a set of type constraints. The+ contraints are in the form of equalities:++ type1 = type2++ These constraints are then passed to a solver+ which simplifies them as much as possible.++ If a constraint can't be solved it will appear+ in the solution. For example:++ Int = Bool++ Thus, the type you get back+ is really a set of constraints, rather than+ the (more traditional) single type or type+ error. This type checker never gives errors!++ Primary Authors: Bernie Pope++-------------------------------------------------------------------------------}++{-+ This file is part of baskell.++ baskell 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.++ baskell 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 baskell; if not, write to the Free Software+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA+-}++module TypeCheck+ ( typeCheckExpression+ , typeCheckProgram+ , renderConstraints+ , Constraint+ , Binding (..)+ , SolverType (..)+ )+ where++import AST+ ( Ident+ , Exp (..)+ , Lit (..)+ , Decl (..)+ , Program (..)+ )++import qualified Data.Map as Map+ ( Map+ , empty+ , fromList+ , union+ , insert+ , lookup+ )++import Pretty+ ( Pretty (..)+ , parensIf+ , text+ , (<+>)+ , render+ , vcat+ , Doc+ , parens+ , cat+ , (<>)+ , punctuate+ , comma+ , brackets+ , int+ , empty+ , ($$)+ )++import Data.List+ ( mapAccumL+ , find+ , delete+ )++import Depend+ ( depend )++import qualified Type+ ( Type (..) )++import Utils+ ( nameSupply )++import Control.Monad+ ( zipWithM+ , liftM+ , liftM2+ , unless+ )++import Control.Monad.State+ ( runStateT+ , get+ , put+ , StateT+ , gets+ , modify+ , execStateT+ )++import Control.Monad.Trans+ ( lift+ , liftIO+ )++import Control.Monad.Reader+ ( ReaderT+ , local+ , ask+ , runReaderT+ )++--------------------------------------------------------------------------------++data SolverType+ = TypeOf Binding Ident -- type of an identifier+ | TVar Int+ | TInt+ | TChar+ | TBool+ | TList SolverType+ | TFun SolverType SolverType+ | TTuple [SolverType]+ deriving (Eq, Show)++-- how an identifier is bound+data Binding+ = Free -- not bound at all+ | LamBound -- bound in a lambda abstraction+ | LetBound -- bound in a function declaration (top level)+ deriving (Eq, Show)++type BinderEnv = Map.Map Ident Binding++-- an equality constraint on types+type Constraint = (SolverType, SolverType)++-- infer the type of an expression from the command line+-- print out the type. An initial set of assumptions tell the+-- types of functions in scope+typeCheckExpression :: [Constraint] -> Exp -> IO ()+typeCheckExpression assumptions exp = do+ let initialCount = 0+ initialType = TVar initialCount+ initialConstraint = (reservedIdent, initialType)+ (constraints, finalCount)+ <- runTC (typeExp initialType exp) (initialCount + 1) Map.empty+ let initialStore+ = Store+ { store_active = initialConstraint:constraints+ , store_solution = []+ , store_assumptions = assumptions+ , store_count = finalCount+ }+ store <- runSolve solve initialStore+ putStrLn $ render $ prettyTypeOfExp $ store_solution store++-- pretty print the type infered for an expression on the+-- command line+prettyTypeOfExp :: [Constraint] -> Doc+prettyTypeOfExp cs+ = case find typeOfReservedIdent cs of+ Nothing -> empty+ Just c@(_typeOf, theType)+ -> prettyTypeSol (theType, delete c cs)+ where+ typeOfReservedIdent :: Constraint -> Bool+ typeOfReservedIdent (t1@(TypeOf LetBound ident), _t2)+ = t1 == reservedIdent+ typeOfReservedIdent otherConstraint = False+ prettyTypeSol :: (SolverType, [Constraint]) -> Doc+ prettyTypeSol (t, []) = prettyType t+ prettyTypeSol (t, cs@(_:_))+ = text "if" $$+ indent (vcat $ map prettyConstraint cs) $$+ text "then" $$+ indent (pretty t)+ indent :: Doc -> Doc+ indent doc = text " " <> doc++-- infer the types for a whole program+-- the decls must be sorted into dependency order+typeCheckProgram :: [Constraint] -> Program -> IO [Constraint]+typeCheckProgram assumptions (Program decls) = do+ store <- runSolve (typeDeclss (depend decls)) initialStore+ return $ store_solution store+ where+ initialStore = Store+ { store_active = []+ , store_solution = []+ , store_assumptions = assumptions+ , store_count = 0+ }++--------------------------------------------------------------------------------+-- infer the types of declarations in dependency order+-- type solutions of earlier declarations become+-- type assumptions of later declarations+-- thus if f depends on g, g will be typed first+-- and its type will be an assumption when f is typed+typeDeclss :: [[Decl]] -> Solve ()+typeDeclss dss = mapM_ typeDecls dss++typeDecls :: [Decl] -> Solve ()+typeDecls ds = do+ count <- gets store_count+ (constraints, nextCount) <- liftIO $ runTC (mapM typeDecl ds) count Map.empty+ modify $ \store -> store { store_count = nextCount }+ updateActive $ concat constraints+ solve+ solution <- gets store_solution+ updateAssumptions solution+ modify $ \store -> store { store_active = [] }++--------------------------------------------------------------------------------++type TcState = Int+type TC a = ReaderT BinderEnv (StateT TcState IO) a++runTC :: TC a -> TcState -> BinderEnv -> IO (a, TcState)+runTC action state env = runStateT (runReaderT action env) state++freshVar :: TC SolverType+freshVar = do+ count <- lift get+ lift $ put (count + 1)+ return $ TVar count++extendEnv :: Ident -> Binding -> TC a -> TC a+extendEnv ident binding action =+ local (Map.insert ident binding) action++lookupIdentBinding :: Ident -> TC Binding+lookupIdentBinding ident = do+ env <- ask+ return $ case Map.lookup ident env of+ Just bind -> bind+ Nothing -> Free++-- type a single declaration+typeDecl :: Decl -> TC [Constraint]+typeDecl (Sig {}) = return [] -- XXX+typeDecl (Decl ident body) = do+ newVar <- freshVar+ cs <- typeExp newVar body+ let c1 = (TypeOf LetBound ident, newVar)+ return $ c1:cs++-- type expressions+-- * arg1 maps vars to their binding style+-- * arg2 is the expected type of this expression,+-- as required by its context+-- * arg3 is the expression itself+typeExp :: SolverType -> Exp -> TC [Constraint]+typeExp t (Var ident) = do+ binding <- lookupIdentBinding ident+ return [(TypeOf binding ident, t)]++-- XXX can we avoid the need to introduce t2?+typeExp t (Lam ident body) = do+ t1 <- freshVar+ t2 <- freshVar+ let c1 = (t, TFun (TypeOf LamBound ident) t1)+ c2 = (t2, TypeOf LamBound ident)+ csBody <- extendEnv ident LamBound $ typeExp t1 body+ return $ [c1, c2] ++ csBody++typeExp t (LamStrict ident body) = do+ t1 <- freshVar+ t2 <- freshVar+ let c1 = (t, TFun (TypeOf LamBound ident) t1)+ c2 = (t2, TypeOf LamBound ident)+ csBody <- extendEnv ident LamBound $ typeExp t1 body+ return $ [c1, c2] ++ csBody++typeExp t exp@(App e1 e2) = do+ t1 <- freshVar+ csRight <- typeExp t1 e2+ csLeft <- typeExp (TFun t1 t) e1+ return $ csLeft ++ csRight++typeExp t (Literal lit) = typeLit t lit++-- XXX delete this?+typeExp t (Tuple exps) = do+ let dimension = length exps+ vars <- sequence $ replicate dimension freshVar+ let c1 = (t, TTuple vars)+ cssExps <- typeExpList vars exps+ return $ c1 : concat cssExps++-- primitives don't give rise to constraints+-- their types are already known+typeExp t (Prim _name _impl) = return []++-- XXX delete this? Is it some kind of mapM, or mapAccum ?+-- an list expression+typeExpList :: [SolverType] -> [Exp] -> TC [[Constraint]]+typeExpList ts es = zipWithM typeExp ts es++-- literals+-- * arg1 is the expected type of this literal,+-- as required by its context+-- * arg2 is the literal itself++typeLit :: SolverType -> Lit -> TC [Constraint]+typeLit t (LitInt _i) = return [(t, TInt)]++typeLit t (LitChar _c) = return [(t, TChar)]++typeLit t (LitBool _b) = return [(t, TBool)]++typeLit t LitCons = do+ t1 <- freshVar+ return [(t, TFun t1 (TFun (TList t1) (TList t1)))]++typeLit t LitNil = do+ t1 <- freshVar+ return [(t, TList t1)]++--------------------------------------------------------------------------------+-- constraint resolution++type Solve a = StateT Store IO a++runSolve :: Solve () -> Store -> IO Store+runSolve action store = execStateT action store++-- the constraint store+data Store+ = Store+ { store_active :: [Constraint] -- not yet solved+ , store_solution :: [Constraint] -- solved in this pass+ , store_assumptions :: [Constraint] -- prior assumptions+ , store_count :: Int -- counter for generating fresh vars+ }+ deriving (Eq, Show)++-- keep reducing the store until there are no active constraints left+solve :: Solve ()+solve = do+#ifdef DEBUG+ -- store <- get+ -- liftIO $ debugPrintStore store+#endif+ active <- gets store_active+ unless (null active) $ do+ modify $ \store -> store { store_active = tail active }+ applyRule $ head active+ solve++-- eliminate a given active constraint+-- there are three situations to consider, the contraint deals with:+-- 1) the type of an identifier (typeOf x = Bool)+-- 2) a type variable (tvar 12 = Char)+-- 3) an equality between two concrete types (List (tvar 24) = List Int)+applyRule :: Constraint -> Solve ()+applyRule c@(t1, t2)+ = case c of+ -- type of identifier constraints+ (TypeOf {}, _) -> typeOfRule c+ (_, TypeOf {}) -> typeOfRule (t2, t1)+ -- type variable contraints+ (TVar {}, _) -> substitute c+ (_, TVar {}) -> substitute (t2, t1)+ -- constraints on concrete types+ (_, _) -> match (t1, t2)++-- resolve contraints on types of identifiers+-- the variable could be:+-- * free+-- * let bound+-- * lambda bound+typeOfRule :: Constraint -> Solve ()+typeOfRule (t1@(TypeOf binding ident), t2)+ | binding == Free = freeIdent (t1, t2)+ | binding == LetBound = updateSolution [(t1, t2)]+ | binding == LamBound = substitute (t1, t2)++-- a free identifier could be typed in the assumptions+-- or it may be unknown. If it is in the assumptions+-- then replace all occurrences of t2 with an *instance*+-- of the type found in the assumptions.+freeIdent :: Constraint -> Solve ()+freeIdent (t1@(TypeOf _binding ident), t2) = do+ store <- get+ let assumptions = store_assumptions store+ case lookupAssumption assumptions ident of+ Just scheme -> applyScheme (scheme, t2)+ Nothing -> do+ let solAndActive+ = store_active store ++ store_solution store+ newConstraints+ = [ (t2, t3) | t3 <- lookupFreeIdent solAndActive ident ]+ if null newConstraints+ then updateSolution [(t1, t2)]+ else updateActive newConstraints++-- look for a type assumption for an identifier in+-- a set of contraints+lookupAssumption :: [Constraint] -> Ident -> Maybe SolverType+lookupAssumption [] _key = Nothing+lookupAssumption ((TypeOf _binding ident, t) : cs) key+ | ident == key = Just t+ | otherwise = lookupAssumption cs key+lookupAssumption (_other : cs) key+ = lookupAssumption cs key++lookupFreeIdent :: [Constraint] -> Ident -> [SolverType]+lookupFreeIdent [] _key = []+lookupFreeIdent (c@(t1, t2) : cs) key+ = case c of+ (TypeOf _binding ident, _)+ -> if ident == key then t2 : rest else rest+ (_, TypeOf _binding ident)+ -> if ident == key then t1 : rest else rest+ (_, _) -> rest+ where+ rest = lookupFreeIdent cs key++-- apply a type scheme to the contraint store+applyScheme :: Constraint -> Solve ()+applyScheme (scheme, t) = do+ schemeInstance <- typeInstance scheme+ updateActive [(schemeInstance, t)]++-- match two concrete types. This might generate new active+-- contraints if either of the types has arguments+match :: Constraint -> Solve ()+match (t1@(TVar i), t2@(TVar j))+ | i == j = return ()+ | otherwise = updateActive [(t1, t2)]+match (TInt, TInt) = return ()+match (TChar, TChar) = return ()+match (TBool, TBool) = return ()+match (TList t1, TList t2) = updateActive [(t1, t2)]+match (TFun t1 t2, TFun t3 t4) = updateActive [(t1, t3), (t2, t4)]+match (t1@(TTuple ts1), t2@(TTuple ts2))+ | length ts1 == length ts2 = updateActive (zip ts1 ts2)+ -- type error+ | otherwise = updateSolution [(t1, t2)]+match (t1@(TypeOf _ _), t2@(TypeOf _ _))+ | t1 == t2 = return ()+ | otherwise = updateActive [(t1, t2)]+-- type error+match (t1, t2) = updateSolution [(t1, t2)]++-- substitute a type variable or a typeOf with+-- another type in the store+substitute :: Constraint -> Solve ()+substitute (t1, t2)+ | t1 == t2 = return ()+ -- occurs check failure, infinite type+ | occursInType t1 t2 = updateSolution [(t1, t2)]+ | otherwise = do+ store <- get+ let newActives = map (subTypeInConstraint t1 t2) (store_active store)+ newSolution = map (subTypeInConstraint t1 t2) (store_solution store)+ put $ store { store_active = newActives+ , store_solution = newSolution }+++subTypeInConstraint :: SolverType -> SolverType -> Constraint -> Constraint+subTypeInConstraint t1 t2 (typeLeft, typeRight)+ = (newLeftType, newRightType)+ where+ newLeftType = subTypeInType t1 t2 typeLeft+ newRightType = subTypeInType t1 t2 typeRight++subTypeInType :: SolverType -> SolverType -> SolverType -> SolverType+subTypeInType old new thisType@(TVar _)+ | thisType == old = new+ | otherwise = thisType+subTypeInType old new (TList t)+ = TList $ subTypeInType old new t+subTypeInType old new (TFun t1 t2)+ = TFun newT1 newT2+ where+ newT1 = subTypeInType old new t1+ newT2 = subTypeInType old new t2+subTypeInType old new (TTuple ts)+ = TTuple newTs+ where+ newTs = map (subTypeInType old new) ts+subTypeInType old new thisType@(TypeOf _ _)+ | thisType == old = new+ | otherwise = thisType+subTypeInType old new otherType = otherType++-- make a fresh instance of an existing type.+-- instance has same shape as existing type+-- but all variables are fresh++type TyVarMap = Map.Map Int Int+type Inst a = StateT (TyVarMap, Int) IO a++lookupTyVarMap :: Int -> Inst (Maybe Int)+lookupTyVarMap i = do+ map <- gets fst+ return $ Map.lookup i map++freshTyVarCounter :: Inst Int+freshTyVarCounter = do+ count <- gets snd+ modify $ \(map, count) -> (map, count+1)+ return count++extendTyVarMap :: Int -> Int -> Inst ()+extendTyVarMap x y =+ modify $ \(map, count) -> (Map.insert x y map, count)++typeInstance :: SolverType -> Solve SolverType+typeInstance t = do+ count <- gets store_count+ (resultType, (_env, finalCount))+ <- liftIO $ runStateT (mkInstance t) (Map.empty, count)+ modify $ \store -> store { store_count = finalCount }+ return resultType+ where+ mkInstance :: SolverType -> Inst SolverType+ mkInstance (TVar var) = do+ mbVar <- lookupTyVarMap var+ case mbVar of+ Nothing -> do+ count <- freshTyVarCounter+ extendTyVarMap var count+ return $ TVar count+ Just newVar -> return $ TVar newVar+ mkInstance (TList t) = liftM TList $ mkInstance t+ mkInstance (TFun t1 t2) = liftM2 TFun (mkInstance t1) (mkInstance t2)+ mkInstance (TTuple ts) = liftM TTuple $ mapM mkInstance ts+ mkInstance otherType = return otherType++-- does a type var or typeOf occur within another type?+occursInType :: SolverType -> SolverType -> Bool+occursInType search thisType@(TVar _)+ = search == thisType+occursInType search (TList t)+ = occursInType search t+occursInType search (TFun t1 t2)+ = occursInType search t1 || occursInType search t2+occursInType search (TTuple ts)+ = any (occursInType search) ts+occursInType search thisType@(TypeOf _ _)+ = search == thisType+occursInType search other = False++-- update the solution constraints in the store+updateSolution :: [Constraint] -> Solve ()+updateSolution cs = do+ oldSolution <- gets store_solution+ modify $ \store -> store { store_solution = cs ++ oldSolution }++-- update the active constraints in the store+updateActive :: [Constraint] -> Solve ()+updateActive cs = do+ oldActive <- gets store_active+ modify $ \store -> store { store_active = cs ++ oldActive }++-- update the active constraints in the store+updateAssumptions :: [Constraint] -> Solve ()+updateAssumptions cs = do+ oldAssumps <- gets store_assumptions+ modify $ \store -> store { store_assumptions = cs ++ oldAssumps }++--------------------------------------------------------------------------------++-- pretty printing of types and constraints++debugPrintStore :: Store -> IO ()+debugPrintStore store+ = do+ putStrLn "---- the current store ----"+ putStrLn "active constraints:"+ putStrLn $ renderConstraintsUgly $ store_active store+ putStrLn "solution:"+ putStrLn $ renderConstraintsUgly $ store_solution store+ -- putStrLn "assumptions:"+ -- putStrLn $ renderConstraints $ store_assumptions store+ putStr "count: "+ print $ store_count store+ return ()++data PrettyState+ = PrettyState+ { prettyState_varMap :: Map.Map Int String+ , prettyState_nameSupply :: [String]+ }++initPrettyState :: PrettyState+initPrettyState+ = PrettyState+ { prettyState_varMap = Map.empty+ , prettyState_nameSupply = nameSupply+ }++instance Pretty SolverType where+ pretty = prettyType++-- pretty printing of types, type variables get nice names+prettyType :: SolverType -> Doc+prettyType = snd . prettyTypeWorker False initPrettyState++prettyTypeWorker :: Bool -> PrettyState -> SolverType -> (PrettyState, Doc)+prettyTypeWorker _bracks state (TVar i)+ = case Map.lookup i varMap of+ Nothing+ -> (newState, text newName)+ Just name -> (state, text name)+ where+ varMap = prettyState_varMap state+ nameSupply = prettyState_nameSupply state+ newName = head nameSupply+ newState+ = PrettyState+ { prettyState_varMap = Map.insert i newName varMap+ , prettyState_nameSupply = tail nameSupply+ }+prettyTypeWorker _bracks state TInt = (state, text "Int")+prettyTypeWorker _bracks state TChar = (state, text "Char")+prettyTypeWorker _bracks state TBool = (state, text "Bool")+prettyTypeWorker _bracks state (TList t)+ = (newState, brackets doc)+ where+ (newState, doc) = prettyTypeWorker False state t+prettyTypeWorker bracks state (TFun t1 t2)+ = (newState, doc)+ where+ (t1State, t1Doc) = prettyTypeWorker True state t1+ (newState, t2Doc) = prettyTypeWorker False t1State t2+ doc = parensIf bracks (t1Doc <+> text "->" <+> t2Doc)+prettyTypeWorker _bracks state (TTuple ts)+ = (newState, doc)+ where+ (newState, tsDoc) = mapAccumL (prettyTypeWorker False) state ts+ doc = parens $ cat $ punctuate comma tsDoc+prettyTypeWorker _bracks state (TypeOf binding ident)+ = (state, doc)+ where+ doc = text "type" <> (parens $ prettyBinder binding <+> text ident)++-- less pretty printing of types. Type variables do not get nice+-- names, they are printed as their underlying numbers. This is+-- helpful for debugging the constraint solver.+uglyType :: Bool -> SolverType -> Doc+uglyType _bracks (TVar i)+ = text "t" <> int i+uglyType _bracks TInt = text "Int"+uglyType _bracks TChar = text "Char"+uglyType _bracks TBool = text "Bool"+uglyType _bracks (TList t)+ = brackets $ uglyType False t+uglyType bracks (TFun t1 t2)+ = parensIf bracks (t1Doc <+> text "->" <+> t2Doc)+ where+ t1Doc = uglyType True t1+ t2Doc = uglyType False t2+uglyType _bracks (TTuple ts)+ = parens $ cat $ punctuate comma tsDoc+ where+ tsDoc = map (uglyType False) ts+uglyType _bracks (TypeOf binding ident)+ = text "type" <> (parens $ prettyBinder binding <+> text ident)++prettyBinder :: Binding -> Doc+prettyBinder Free = text "free"+prettyBinder LamBound = text "lambda-bound"+prettyBinder LetBound = text "let-bound"++reservedIdent :: SolverType+reservedIdent = TypeOf LetBound "$"++uglyConstraint :: Constraint -> Doc+uglyConstraint (t1, t2)+ = uglyType False t1 <+> text "=" <+> uglyType False t2++prettyConstraint :: Constraint -> Doc+prettyConstraint+ = snd . prettyConstraintWorker initPrettyState++prettyConstraintWorker :: PrettyState -> Constraint -> (PrettyState, Doc)+prettyConstraintWorker state (TypeOf LetBound ident, t2)+ = (newState, doc)+ where+ (newState, t2Doc) = prettyTypeWorker False state t2+ doc = text ident <+> text "::" <+> t2Doc+prettyConstraintWorker state (t1, t2)+ = (newState, doc)+ where+ (t1State, t1Doc) = prettyTypeWorker False state t1+ (newState, t2Doc) = prettyTypeWorker False t1State t2+ doc = t1Doc <+> text "=" <+> t2Doc++renderConstraints :: [Constraint] -> String+renderConstraints cs+ = render $ vcat $ map prettyConstraint cs++renderConstraintsUgly :: [Constraint] -> String+renderConstraintsUgly cs+ = render $ vcat $ map uglyConstraint cs++--------------------------------------------------------------------------------++toSolverType :: Type.Type -> SolverType+toSolverType (Type.TVar i) = TVar i+toSolverType Type.TInt = TInt+toSolverType Type.TChar = TChar+toSolverType Type.TBool = TBool+toSolverType (Type.TList t) = TList $ toSolverType t+toSolverType (Type.TFun t1 t2) = TFun (toSolverType t1) (toSolverType t2)+toSolverType (Type.TTuple ts) = TTuple $ map toSolverType ts
+ src/Utils.hs view
@@ -0,0 +1,41 @@+{-------------------------------------------------------------------------------++ Copyright: Bernie Pope 2004++ Module: Utils ++ Description: Generally useful things without a specific+ home ++ Primary Authors: Bernie Pope++-------------------------------------------------------------------------------}++{-+ This file is part of baskell.++ baskell 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.++ baskell 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 baskell; if not, write to the Free Software+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA+-}++module Utils + ( nameSupply )+ where++--------------------------------------------------------------------------------++nameSupply :: [String]+nameSupply+ = [ x ++ [y] | x <- []:nameSupply, y <- ['a'..'z']]+