[Haskell-cafe] |> vs. $ (was: request for code review)
Daniel Fischer
daniel.is.fischer at web.de
Wed Mar 8 06:01:08 EST 2006
Am Dienstag, 7. März 2006 20:52 schrieb Shannon -jj Behrens:
> I did think of using a monad, but being relatively new to Haskell, I
> was confused about a few things. Let's start by looking at one of my
> simpler functions:
>
> -- Keep pushing tokens until we hit an identifier.
> pushUntilIdentifier :: ParseContextTransformation
> pushUntilIdentifier ctx
>
> | currTokType ctx == Identifier = ctx
> | otherwise =
>
> let newStack = (currTok ctx) : (stack ctx) in
> (ctx {stack=newStack}) |>
> getToken |>
> pushUntilIdentifier
>
> The function itself is a ParseContextTransformation. It takes a
> context, transforms it, and returns it. Most of the pipelines in the
> whole application are ParseContextTransformations, and the |> (or $ or
> .) are ways of tying them together. My questions concerning Monads
> are in this example are:
>
> 1. Monads apply a strategy to computation. For instance, the list
> monad applies the strategy, "Try it with each of my members." What
> part of my code is the strategy?
>
> 2. Monads are containers that wrap a value. For instance, the Maybe
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Some are, others embody computations that produce a value, yet others: ?
> monad can wrap any value, or it can wrap no value and just be Nothing.
> What part of my code is the thing being wrapped, and what part is
> "extra data" stored in the Monad itself?
>
> So I guess:
>
> 3. Is the ParseContext the monad or the thing being wrapped?
>
> 4. How do I divide the code between the functions on the right side of
>
> >>= and the functions in the monad itself? The functions on the right
>
> side of >>= operate on the stuff inside the monad, and the functions
> in the monad itself operate on the stuff in the monad.
>
> 5. How does the ParseContextTransformation relate?
>
> It is because I did not understand the answers to these questions that
> I thought maybe a monad might not be appropriate. However, I surely
> could be wrong. Afterall, ParseContext, ParseContextTransformation,
> and |> are all *inspired* by what I knew about monads.
>
> Thanks for your help!
>
> -jj
I'd use a State-monad, say
import Control.Monad.State
type CDParser a = State ParseContext a
-- or perhaps StateT ParseContext m a, where m is an appropriate monad,
-- I haven't thought much about it
then you'd have e.g.
pushUntilIdentifier :: CDParser ()
pushUntilIdentifier = do
tt <- gets currTokType
case tt of
Identifier -> return ()
_ -> do
pushToken
getToken
pushUntilIdentifier
okay, that doesn't look really better, but if you'd done it monadically from
the start, you'd probably chosen a different design (I think, I'd leave the
current token out of the ParseContext and have that returned by the
appropriate actions). Alternatively, you could use Parsec with Parsecontext
as user state (removing the input from ParseContext) and take advantage of
the many provided combinators in Parsec.
As another method, I've hacked up a translation by parsing a declaration and
creating a customized Show-instance. It could be much improved, but for a
quick hack, I can live with it.
-- | Translate C-declarations to english, well, sort of
module Translate where
import Text.ParserCombinators.ReadPrec
import qualified Text.ParserCombinators.ReadP as P
import Text.Read
import Data.Char (isAlpha, isAlphaNum)
-- lift some operators from ReadP to ReadPrec, indicates that I
-- should have originally worked with ReadP and lifted to
-- ReadPrec afterwards.
spaces = lift P.skipSpaces
string = lift . P.string
char = lift . P.char
many p = lift $ P.many $ readPrec_to_P p 0
-- | list of known types, struct, union and enum don't really
-- belong here, but since C is inherently sick, it doesn't matter
typeNames :: [String]
typeNames = [ "void", "char", "signed", "unsigned", "short", "int"
, "long", "float", "double", "struct", "union", "enum"]
-- | may this Char appear in a C-identifier?
isIdLetter :: Char -> Bool
isIdLetter c = c == '_' || isAlphaNum c
-- | may this Char begin a C-identifier?
isIdStart :: Char -> Bool
isIdStart c = c == '_' || isAlpha c
-- | the sort of types, we can handle
data CType
= Basic String -- ^ plain types like int, char...
| Const CType -- ^ type with "const"
| Ptr CType -- ^ pointer to type
| Array [Maybe Int] CType -- ^ Array with dimensions
-- | type synonym to check whether a variable is volatile
type Volatile = Bool
-- | the declarations we can parse, due to C's horrible syntax,
-- we can't handle multiple variable declarations like
--
-- > int *a, b[5], c;
--
-- but the original programme couldn't either.
data Decl
= VarDecl CType Volatile String
| FunDecl CType String [CType]
----------------------------------------------------------------------
-- Show Instances --
----------------------------------------------------------------------
-- here we translate the declaration to english
instance Show CType where
showsPrec _ (Basic nm)
= showString nm
showsPrec _ (Const ty)
= showString "read-only " . shows ty
showsPrec _ (Ptr ty)
= showString "pointer to " . shows ty
showsPrec _ (Array dims ty)
= showD dims . shows ty
where
showD [] = id
showD (Just n:ds)
= showString "array 0.." . shows (n-1) . showString " of "
. showD ds
showD (Nothing:ds)
= showString "array of " . showD ds
instance Show Decl where
showsPrec _ (VarDecl ty vol nm)
= showString nm . showString " is a " . showV vol .
showString "variable of type " . shows ty
where
showV True = showString "volatile "
showV False = id
showsPrec _ (FunDecl rty nm atys)
= showString nm . showString " is a function of " .
showArgs atys . showString ", returning " . shows rty
where
showArgs []
= showString "no arguments"
showArgs [t]
= showString "one argument of type " . shows t
showArgs tys@(t:ts)
= shows (length tys) . showString " arguments of types " .
shows t . showRest ts
showRest [t] = showString " and " . shows t
showRest (t:ts) = showString ", " . shows t . showRest ts
----------------------------------------------------------------------
-- Parsing --
----------------------------------------------------------------------
lexeme :: String -> ReadPrec String
lexeme str = do
spaces
string str
rst <- look
case rst of
(c:_) | isIdLetter c -> pfail
_ -> return str
parseIdentifier :: ReadPrec String
parseIdentifier = lift $ do
P.skipSpaces
c <- P.satisfy isIdStart
cs <- P.many (P.satisfy isIdLetter)
return (c:cs)
parseBasic :: ReadPrec CType
parseBasic = do
tynam <- choice $ map lexeme typeNames
return (Basic tynam)
parseNoArray :: ReadPrec CType
parseNoArray = (do
ty <- parsePType
lexeme "const"
return (Const ty)) <++ parsePType
parseConstT :: ReadPrec CType
parseConstT = do
lexeme "const"
bs <- parseBasic
return (Const bs)
parsePrePtr :: ReadPrec CType
parsePrePtr = parseConstT <++ parseBasic
parsePType :: ReadPrec CType
parsePType = do
ty <- parsePrePtr
complete ty
where
complete t
= (do
spaces
char '*'
complete (Ptr t)) <++ return t
parseDim :: ReadPrec (Maybe Int)
parseDim = (do
spaces
char '['
n <- readPrec
spaces
char ']'
return (Just n)) <++ (spaces >> string "[]" >> return Nothing)
parseVarDecl :: ReadPrec Decl
parseVarDecl = do
ty <- parseNoArray
vol <- (lexeme "volatile" >> return True) <++ return False
nam <- parseIdentifier
ds <- many parseDim
spaces
char ',' <++ char ';' <++ escape
let t = if null ds then ty else Array ds ty
return (VarDecl t vol nam)
where
escape = do
rst <- look
case rst of
(')':_) -> return '.'
_ -> pfail
parseFunDecl :: ReadPrec Decl
parseFunDecl = do
ty <- parsePType
nam <- parseIdentifier
ats <- parseFuncArgs
spaces
char ';' <++ char '{'
return (FunDecl ty nam ats)
parseFuncArgs :: ReadPrec [CType]
parseFuncArgs = do
char '('
vds <- many parseVarDecl
char ')'
return (map typ vds)
where
typ (VarDecl ty _ _) = ty
instance Read Decl where
readPrec = parseFunDecl <++ parseVarDecl
readDecl :: String -> Decl
readDecl = read
translate :: String -> String
translate = show . readDecl
--------------------------------------------------------------------------
It's better than the original for some things:
*CDecl> translate "const int * const a;"
"a is read-only"
*CDecl> :l Translate
Compiling Translate ( Translate.hs, interpreted )
Ok, modules loaded: Translate.
*Translate> translate "const int * const a;"
"a is a variable of type read-only pointer to read-only int"
but
*CDecl> translate "union {int a, char b};"
"a is int"
*Translate> translate "union {int a, char b};"
"*** Exception: Prelude.read: no parse
Neither is really convincing.
Cheers,
Daniel
--
"In My Egotistical Opinion, most people's C programs should be
indented six feet downward and covered with dirt."
-- Blair P. Houghton
More information about the Haskell-Cafe
mailing list