[Haskell-beginners] Indenting

Brent Yorgey byorgey at seas.upenn.edu
Sat Dec 5 15:09:01 EST 2009


On Sat, Dec 05, 2009 at 05:58:25PM +0000, John Moore wrote:
> Hi Brent,
> Sorry about the confusion below is the whole program, which may help or not?
> What i want to do is when the program prints out the answers , I would like
> it to be able to automatically by use of a function indent the answers
> depending how far down the answer goes.

OK, I think I understand what you want to do, although I'm not exactly
clear on how you want the indentation to work.

Anyway, I've interspersed my comments in the code below.
 
> import Maybe

Just FYI, "Maybe" is the old Haskell-98 name for the module, but
nowadays the standard name is "Data.Maybe".

> data Expression = Val Float
>                 | Add Expression Expression
>                 | Subtract Expression Expression
>                 | Multiply Expression Expression
>                 | Divide Expression Expression
>   | Let String Expression Expression
>   | Var String
>          deriving Show
> type Dict =[(String,Expression)]
> emptyDict :: Dict
> emptyDict = []
> addEntry :: String->Expression ->Dict -> Dict
> addEntry n e d = (n,e): d
> lookupEntry :: String -> Dict -> Maybe Expression
> lookupEntry n [] = Nothing
> lookupEntry n (x:xs) = if (n == k)
>    then (Just v)
>                 else lookupEntry n xs
>    where (k,v) = x

I would write lookupEntry as follows:

  lookupEntry _ [] = Nothing
  lookupEntry n ((k,v):xs) | n == k    = Just v
                           | otherwise = lookupEntry n xs

Note that you can do a nested pattern match ((k,v):xs), and the use of
guards instead of if...then...else.

But actually, lookupEntry is already in the standard Prelude, it is
called 'lookup'!  So no need to reimplement it yourself.

> evalIO :: Dict -> Expression -> IO Float

This function is rather poor Haskell style, because it mixes up two
separate things: evaluating the expression, and printing the
result. Instead, I would do something like this:

  eval :: Dict -> Expression -> ([String], Double)

where the output is the result paired with a "trace".  (Note I have
used Double, a double-precision floating point number, instead of
Float, which is single-precision; there's usually very little reason
to use Float instead of Double.)  Given eval, you can recover evalIO as follows:

  evalIO :: Dict -> Expression -> IO Double
  evalIO d e = do
    let (trace, result) = eval d e
    mapM_ putStrLn trace
    return result

But this is a lot nicer because it cleanly separates the evaluation
from the IO, and you can now do anything you like with the trace ---
print it to a file, process it further, etc; you are not tied down to
printing it on the screen.

Now, if you just want the indentation to increase at each step, like so

xxxxx
 xxxxx
  xxxxx
   xxxxx
    xxxxx
     ...

then you can also separate this out: just return a trace with no
indentation, and then apply a function to the trace to indent
successive lines, something like

  zipWith (++) (map (flip replicate ' ') [0..]) trace

If, on the other hand, you want the indentation to correspond to how
"deep" within the expression the evaluation is taking place, then you
can recursively pass along an extra parameter to your evaluation
function, like so:

  eval :: Dict -> Expression -> ([String], Double)
  eval d e = evalIndented 0 d e where
    evalIndented i d (Val x) = ([], x)
    evalIndented i d (Add x y) = 
      let (tx, vx) = evalIndented (i+1) d x
          (ty, vy) = evalIndented (i+1) d y
      in  (replicate i ' ' ++ "Add " ++ show vx ++ " and " ++ show vy, vx + vy)

...and so on.

I hope this is helpful!

-Brent


More information about the Beginners mailing list