Basically I'm expanding a literate program in Haskell. There are two things I need to look for to expand. Here's an example file:

Some comments in the source file that I can ignore
    code.. code ...

Some comments in the source file that I can ignore
    code... code...

In a file, I need to look at each <some var> and substitute it with its matching definition defined below <some var>=. All the lines which are indented with 4 spaces below a <some defn>= are included in that chunk. In the end i should have a file consisting of only code ... lines.

How should I approach this problem in terms of file processing, what's a good approach? I'm comfortable with recursion, less comfortable with monads.

  • why didn't you ask at Stack Overflow? meta.stackexchange.com/a/129632/165773
    – gnat
    Jul 31, 2014 at 17:59
  • 1
    @gnat this seems much like a "whiteboard" problem to me. I'm asking for a design approach, not actual code.
    – cdosborn
    Jul 31, 2014 at 18:12
  • Is the definition of a var contained in one line, to the right of the equal sign?
    – Giorgio
    Jul 31, 2014 at 19:17
  • No, the defn is the indented chunk below <var name>=, until the next unindented portion
    – cdosborn
    Jul 31, 2014 at 20:18

1 Answer 1


I will make a few assumptions in my answer. Feel free to correct me if one of them is incorrect:

  • you are doing this as an exercise
  • you only want to inline the top level definitions
  • there is at most only one definition for a given variable name
  • you want to simplify a given definition by inlining all known variables

If you are doing this for a "real" project, or want to make more complex transformations, I recommend that you look at existing packages such as haskell-src-exts.

Now if you want a robust solution, you will need those functions:

    :: String        -- the input text
    -> [Declaration] -- a datastructure representing the expression
    :: Declaration   -- the definition to inline
    -> Declaration   -- the definition to modify
    -> Declaration   -- the result definition
showDecl :: Declaration -> String
    :: String        -- the input text
    -> String        -- the variable name to simplify
    -> String        -- the result

Declaration can be as simple as (String, String) (the variable name and its definition). Note that I am using loosely typed definitions. I am also using String, which may not be the most efficient solution. You may consider using Text to speed things up.

The parse function can be tricky. I recommend using the parsec library. It uses a monad, but don't feel intimidated by it. Using monads does not require understanding them at a fundamental level. Actually the opposite is true. See here for an introduction. You can view the parsec type as a statement which consume a piece of text and maybe return some result.

parse input = /case analysis of / runParser (many parseBlock) () "source name" input

parseBlock = do

parseDef = do
    varName <- parseVar
    string " = "
    definition <- parseExpr
    return $ Declaration varName defintion

parseExpr = ...

Here, unit tests are your best friends. Be sure that all your smaller parsers are working well before combining them. Pay attention to the backtracking semantics (ie the try combinator) because you can easily shoot yourself in the foot with that.

The inline function is easier. It will just be a well thought out search and replace. The simplify function is just a matter of calling parse, extracting the declaration to simplify, and calling inline with all the other ones on it. No recursion needed.

Alternatively, your inline function can be of type Declaration -> String -> String, and edit directly the input string. This way you won't have to write the showDecl function.

  • There can be multiple extensions of a chunk. By using the same name with the <>= syntax. This is exactly the help I was looking for, thank you.
    – cdosborn
    Aug 1, 2014 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.