Write Yourself a Scheme in 48 Hours

An Introduction to Haskell through Example
by Wikibooks contributors originally by Jonathan Tang

Created on Wikibooks, the open content textbooks collection.

Copyright c 2007 Jonathan Tang & Wikibooks contributors. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled “GNU Free Documentation License”.

Contents
Overview 1 First Steps Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Parsing Writing a Simple Parser . Whitespace . . . . . . . . Return Values . . . . . . . Exercises . . . . . . Recursive Parsers: Adding Exercises . . . . . . . . . . . . . . . . . . lists, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . dotted lists, and quoted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . datums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v 1 4 5 5 6 8 12 13 15 17 17 20 23 27 29 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 37 42 42 48 54 57 65 77 87 i

3 Evaluation, Part 1 Beginning the Evaluator . . Beginnings of an Evaluator: Adding Basic Primitives . . Exercises . . . . . . . . . .

. . . . . . Primitives . . . . . . . . . . . .

4 Error Checking and Exceptions 5 Evaluation, Part 2 Additional Primitives: Partial Application . . . Conditionals: Pattern Matching 2 . . . . . . . . List Primitives: car, cdr, and cons . . . . . . Equal? and Weak Typing: Heterogenous Lists Exercises . . . . . . . . . . . . . . . . . . . . . 6 Building a REPL 7 Adding Variables and Assignment 8 Defining Scheme Functions 9 Creating IO Primitives

. . . . . . . . . . . . 7. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3 . . . . . . . . . . . . . . .10 Towards a Standard Library Conclusion A Complete Parser B Answers to Exercises Section 2. . . . . . . Wikibooks Changes . . . . . . . . . . . . . 2. . . . . . . . . . . . . . . . . . . . . . AGGREGATION WITH INDEPENDENT WORKS . . . . . . . . . . . . . . . . . . . . . . . TERMINATION . . . . . . . . Orignal Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . COLLECTIONS OF DOCUMENTS . . . . . . . . . APPLICABILITY AND DEFINITIONS . . 10. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercise 3 . . . . . . D GNU Free Documentation License 1. . . . . . . . . . . . . . . . . . . . . . . . . . 9. . . . . . . . Exercise 5 . . . 4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercise 2 . Part 1 . . . . . . . . . . . . . . . . . . Part 2 . . . . . . . . . . . . . . . MODIFICATIONS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercise 4 . . . . . . . . . . . COPYING IN QUANTITY . . . . . . . . . . . . . . . . . . . . . . . . . . . . COMBINING DOCUMENTS . . . . . 3. . . ADDENDUM: How to use this License for your documents Index ii . . . . . . . . . . . . . . . . . . . 5. . . . . . . . . . . . . . . . . Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . TRANSLATION . . . . FUTURE REVISIONS OF THIS LICENSE . 6. . . . PDF Information & History Document Formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 101 103 113 113 113 113 113 113 114 114 115 117 117 117 117 118 118 118 119 119 121 121 122 124 124 125 125 125 125 126 127 C Document Information History . . . . . . . . . . . . . . . . . . . . . . . . VERBATIM COPYING . . . . . . . . . . . . . . . . . . . . . . 8. . Exercise 1 .

. . . . .1 8. . .hs datatypeparser. . . . . .Listings 1. . . . . . . . . . . . . . . . . 18 . . . . . . . . . . 80 . . equalparser. . . . . . 44 . .1 3. . . . . . . . . . . . .hs . . . . . . . . . .1 10. . . . . . 11 . . . . . . . .hs . . . . . . . . . . . . . . . 50 . . . .hs evaluator1. errorcheck. . . . . . . . . replparser. . . . . . . . . . . . . . . . . . . . . . . .4 3. .1 7. . . . . . . . . .hs functionparser. .1 A. . . . variableparser. . . . . . . .hs . . . . . . . . . .1 2. . . . . . . .2 5. . . . . . 24 .1 5. . . . 7 . . . . . . . . .hs . . . . . . . . . . . . . . . 97 . . . . .hs .hs . . . . . . . . . operatorparser. . . . . . . . . . . . . . . . . . . . . . . .1 2. . .hs listparser. . . . . . . . . .hs stdlib. . . . 70 . . . . . . . . . . . 38 . . . . . . .2 2.hs simpleparser2. . .3 4. evaluator3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .hs . . . . . . . . . . . . . . . . . . . 1 . 14 . . . . . . . . . . . . . . 33 . . . . . completeparser. . .hs . . . . . . . . . . . . . . . . . . . . . .hs recursiveparser. . . . . . . . . . evaluator2. . .1 5. . . . . . simpleparser1. . . . . . . . . . . 6 . .scm . . . . . . . .hs . . . .3 6. 21 . . 58 . . . . . . . . . . . 103 iii . . . . . . . . . . . . . . . . . . . . . . . . . . .1 hello. . . . . . . . .3 2. . . . .2 3. . . . . . . . . . . . . . . . . . . . . . .

.

dynamic typing. Hugs. Haskell is completely different from those languages. mutable state. Users of a procedural or object-oriented language like C. you’ll learn Haskell’s I/O. and you may need to download additional libraries.Overview Most Haskell tutorials on the web seem to take a language-reference-manual approach to teaching. They show you the syntax of the language. Since each lesson builds on the code written for the previous one. useful program is left to the end. as I gloss over several Scheme and general programming concepts to stay focused on the Haskell. you should be fairly fluent in both Haskell and Scheme. Along the way. and progress to writing a fully-functional Scheme interpreter that implements a good-sized subset of R5RS Scheme. This tutorial assumes that you’ll be using GHC as your Haskell compiler. There’re two main audiences targetted by this tutorial: 1. or Python should beware. • live version • discussion • edit • comment • report an error v . and parsing features. error handling. People who already know Lisp or Scheme and want to learn Haskell 2. and then have you construct a few simple functions at the interactive prompt. The “hard stuff ” of how to write a functioning. because many concepts in them (classes. but have a strong quantitative background and are familiar with computers The second group will likely find this challenging. This tutorial takes a different tack. it’s probably best to go through the lessons in order. however: You’ll have to forget most of what you already know about programming. ’return’) have a significantly different meaning in Haskell. It may work with eg. You’ll start off with command-line arguments and parsing. A good textbook like Structure and Interpretation of Computer Programs or The Little Schemer may help a lot here. or sometimes omitted entirely. Java. functions. a few language constructs. and requires a different way of thinking about programming. but it hasn’t been tested at all. People who don’t know any programming language. By the time you finish. It’s best to go into this tutorial with a blank slate and try not to compare Haskell to imperative languages.

.

Eclipse users might want to try the Function Programming plug-in. it’s time for your first Haskell program.1: Your first Haskell program (hello. On Linux. Windows users can use Notepad or any other text editor: Haskell syntax is fairly Notepad-friendly. " ++ a r g s !! 0) Let’s go through this code. It’s also downloadable from http://www. there is a pretty good Emacs mode. Now. but it must be present for the compiler to generate an executable file.Chapter 1 First Steps: Compiling and Running First. A binary package is probably easiest. Haskell is case-sensitive: module names are always capitalized. unless you really know what you’re doing. Every Haskell program begins with an action called main in a module named Main. It should download and install like any other software package.hs and type the following text: Listing 1. including syntax highlighting and automatic indentation. there’s also a Haskell plugin for Visual Studio using the GHC compiler. Finally.hs) module Main where import System . Environment 5 • live version • discussion • edit • comment • report an error main : : IO ( ) main = do a r g s <− getArgs putStrLn ( " H e l l o . This program will read a name off the command line and then print a greeting. but everything should also work on Windows as long as you know how to use the DOS command line. you’ll need to install GHC. Create a file ending in . though you have to be careful with the indentation.org/ ghc/. For UNIX (or Windows Emacs) users. That module may import others.haskell. it’s often pre-installed or available via apt-get or yum. declarations always uncapitalized. This tutorial was developed on Linux. 1 . The first two lines specify that we’ll be creating a module named Main that imports the System module.

because it’s less to change as we build our program. Haskell is a declarative language: instead of giving the computer a sequence of instructions to carry out. To combine them. and only complains if they differ from what you’ve specified. Basically. Monadic values are often called “actions. and that we want it to read the command line args and print some output. The compiler figures out an execution path that puts everything together. To write one of these definitions. These definitions use various compositions of actions and functions. 2.2 CHAPTER 1. There are two ways to create an IO action: 1. If you’re following along at home. and it’ll evaluate to the same value. The IO type is an instance of something called a monad. Combine two existing IO actions. action The first form takes the result of the action and binds it to name. a monad is a way of saying “there’s some extra information attached to this value. Each line can have one of two forms: 1. represented as () . FIRST STEPS The line main :: IO () is a type declaration: it says that the action main has type IO (). Since we want to do two things. you give it a collection of definitions that tell it how to perform every function it might need. The builtin action getArgs reads the command-line arguments and stores them in a list of strings. Type declarations in Haskell are optional: the compiler figures them out automatically. this property makes it significantly easier to reason about Haskell programs than other languages. These equations behave just like ordinary equations in algebra: you can always substitute the right hand side for the left within the text of the program. The left hand side defines a name. you set it up as an equation. all lined up with the first non-whitespace character after the do. which most functions don’t need to worry about. A do-block consists of a series of lines. name <− action 2.” In this example. you may want to omit them. How will we define our main action? We know that it must be an IO action. In this tutorial. the “extra information” is the fact that this action performs IO. using the return function. Lift an ordinary value into the IO monad. The right hand side defines some composition of other definitions that tells the computer what to do when it encounters the name. and optionally one or more patterns (explained later) that will bind variables. for clarity. The built-in function putStrLn takes a string and writes it to the console.” because the easiest way to think about the IO monad is a sequencing of actions that each might affect the outside world. if the type of the action is IO [String] (an IO action returning a list . we’ll take the second approach. which is a scary name for a not-so-scary concept. I specify the types of all declarations explicitly. and the basic value is nothing. For example. we use a do-block. Called referential transparency .

<. so you can use any of the list functions and operators on them. / h e l l o y o u Jonathan H e l l o . "++). you cannot mix actions of different monad types in the same do-block. > =. ‘ notElem‘ ==. !! ˆ. This operator has different semantics for each monad: in the case of the IO monad. Jonathan The -o option specifies the name of the executable you want to create. ˆˆ. Of course. concatenate it onto the end of the string “Hello. The second form just executes the action. A full table of the standard operators and their precedences follows: Table 1. Because the semantics of this composition depends upon the particular monad used. ” ("Hello. but arguments reversed) Infix Function Application (same as f x. >>= =<< Precedence Associativity Description 9 9 8 7 6 5 5 4 4 3 2 1 1 0 Right Left Right Left Left Right Right Left Left Right Right Left Right Right Function composition List indexing Exponentiation (integer. performing whatever external side-effects that result. as with getArgs). sequencing it with the previous line through the >> (pronounced “bind”) operator. /=.1: Operators and their precedence Operator(s) . Monadic Bind (piping value to next function) Reverse Monadic Bind (same as above. . Strings are lists of characters in Haskell.3 of strings. <= . Division Addition. / +. − : ++ ‘ elem‘. Not-equals. and other relation operators Logical And Logical Or Monadic Bind. it executes the actions sequentially. then name will be bound to the list of strings returned.but right-associative instead of left) $ To compile and run the program. > && || >>. ∗∗ ∗. these actions may themselves be the result of functions or complicated expressions. fractional. hs user>> . and floating-point) Multiplication. we first take index 0 of the argument list (args !! 0). In this example. and then you just specify the name of the Haskell source file. Subtraction Cons (list construction) List Concatenation List Membership Equals. try something like this: user>> ghc −o h e l l o y o u h e l l o . and finally pass that to putStrLn for IO sequencing.

3. Change the program so it reads two arguments from the command line. Play around with different operations. and prints out a message using both of them. . FIRST STEPS Exercises 1. You can use read to convert a string to a number. getLine is an IO action that reads a line from the console and returns it as a string. Change the program so it performs a simple arithmetic operation on the two arguments and prints out the result. Change the program so it prompts for a name.4 CHAPTER 1. reads the name. and then prints that instead of the command line value. and show to convert a number back into a string. 2.

you can compose primitive parsers into more sophisticated productions.Chapter 2 Parsing Writing a Simple Parser Now. we’ll define a parser that recognizes one of the symbols allowed in Scheme identifiers: 9 10 symbol : : P a r s e r Char symbol = oneOf "!$ %&|*+ -/: <=? > @^_ ~# " This is another example of a monad: in this case. Parsec takes care of all of that for us. We need only use the Parsec library function oneOf. etc. the “extra information” that is being hidden is all the info about position in the input stream. which comes with GHC but may need to be downloaded separately if you’re using another compiler. backtracking record. Now. and it’ll recognize a single one of any of the characters in the string passed to it. readExpr is a function (−>) from a String to a String. We name the parameter input. along with 5 . except the spaces [edit section] function. first and follow sets. We’ll be using the Parsec library. P a r s e r C o m b i n a t o r s . Parsec provides a number of pre-built parsers: for example. Let’s define a function to call our parser and handle any possible errors: 12 13 14 15 readExpr : : String −> String readExpr i n p u t = case p a r s e symbol " lisp " i n p u t of Left e r r −> " No match : " ++ show e r r Right v a l −> " Found value " As you can see from the type signature. And as you’re about to see. letter and digit are library functions. whose name conflicts with a function that we’ll be defining later. let’s try writing a very simple parser. P a r s e c hiding ( s p a c e s ) This makes the Parsec library functions available to us. Start by adding this line to the import section: 3 • live version • discussion • edit • comment • report an error import Text . and pass it.

then we bind the error itself to err and return "No match" with the string representation of the error. using the Left constructor to indicate an error and the Right one for a normal value. P a r s e c hiding ( s p a c e s ) 5 main : : IO ( ) main = do a r g s <− getArgs putStrLn ( readExpr ( a r g s !! 0) ) 10 symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . If we get a Right value. P a r s e r C o m b i n a t o r s . so we need to handle the error case. parse can return either the parsed value or an error. ignore it./ : < = ? > @ ^ _ ~ # " readExpr : : String −> String readExpr i n p u t = case p a r s e symbol " l i s p " i n p u t o f Left e r r −> " N o m a t c h : " ++ show e r r Right v a l −> " F o u n d v a l u e " 15 To compile and run this. or else there will be link errors. Finally.. to the Parsec function parse. Following typical Haskell convention. For example: user>> ghc − package p a r s e c −o s i m p l e p a r s e r s i m p l e p a r s e r 1 . Environment import Text . which we will see in much greater detail later on. The current parser chokes if there’s whitespace preceding our symbol: . If we get a Left value (error). Parsec returns an Either data type. you need to specify -package parsec on the command line. / s i m p l e p a r s e r a No match : " lisp " ( l i n e 1 . and return the string "Found value".6 CHAPTER 2..hs) module Main where import System . we need to change our main function to call readExpr and print out the result: 5 6 7 main : : IO ( ) main = do a r g s <− getArgs putStrLn ( readExpr ( a r g s ! ! 0 ) ) The complete code is therefore: Listing 2. we’ll add a series of improvements to our parser that’ll let it recognize progressively more complicated expressions. we bind it to val. PARSING the symbol action we defined above and the name of the parser (“lisp”).of construction to match the result of parse against these alternatives. The case..of construction is an example of pattern matching. We use a case.1: A simpe parsing program (simpleparser1.. / s i m p l e p a r s e r $ Found v a l u e user>> . column 1 ) : u n e x p e c t e d "a" Whitespace [edit section] Next. hs user>> .

Listing 2. now ignoring whitespace (simpleparser2. where we mentioned that it was used behind the scenes to combine the lines of a do-block. Incidentally. column 1 ) : unexpected " " 7 Let’s fix that. it’s intended as a general way to structure computations. then attempt to match the second with the remaining input. Read the documentation for the monad to figure out precisely what it does. so that we ignore whitespace.) 16 17 spaces : : Parser () s p a c e s = skipMany1 s p a c e Just as functions can be passed to functions. Environment import Text . However. but we’ll ignore that for pedagogical purposes.” In general. this is why we included the hiding (spaces) clause when we imported Parsec: there’s already a function spaces in that library. but it doesn’t quite do what we want it to. / s i m p l e p a r s e r " %" No match : " lisp " ( l i n e 1 . to get a Parser that will recognize one or more spaces. Changes are highlighted: 1 2 3 We touched briefly on the >> (“bind”) operator in lesson 2. lets define a parser that recognizes any number of whitespace characters. let’s edit our parse function so that it uses this new parser. P a r s e c hiding ( s p a c e s ) 5 readExpr i n p u t = case p a r s e (spaces >> symbol) " lisp " i n p u t of Left e r r −> " No match : " ++ show e r r Right v a l −> " Found value " main : : IO ( ) main = do a r g s <− getArgs putStrLn ( readExpr ( a r g s !! 0) ) 10 symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . First. Here. so can actions. (For that matter. and so needs to be general enough to accommodate all the different types of computations. bind means “Attempt to match the first parser. bind has completely different semantics in the Parser and IO monads.WHITESPACE user>> .hs) module Main where import System . and fail if either fails. P a r s e r C o m b i n a t o r s . there’s also a parser called lexeme that does exactly what we want. Here we pass the Parser action space to the Parser action skipMany1. bind will have wildly different effects in different monads./ : < = ? > @ ^ _ ~ # " readExpr i n p u t = case p a r s e ( s p a c e s >> symbol ) " l i s p " i n p u t o f Left e r r −> " N o m a t c h : " ++ show e r r Right v a l −> " F o u n d v a l u e " spaces : : Parser () s p a c e s = skipMany1 s p a c e 15 . In the Parser monad. we use it explicitly to combine our whitespace and symbol parsers.2: A simpe parsing program. Now.

also called an improper list. c). a LispVal can be: 1. u n e x p e c t e d "a" expecting space −o s i m p l e p a r s e r s i m p l e p a r s e r 2 . we learn how to define a data type. Instead you have to precede a symbol with some whitespace. u n e x p e c t e d "%" expecting space user>> . We’ll see how this is useful shortly: user>> ghc − package p a r s e c user>> . we want something more out of our parsers: we want them to convert the input into a data structure that we can traverse easily. and how to modify our parser so that it returns this data type. Each alternative (called a constructor and separated by | ) contains a tag for the constructor along with the type of data that that constructor can hold. which stores a String naming the atom 2. A Number. PARSING Compile and run this code. also called a proper list 3. First. and then stores the last element as another field 4. In this section. the parser doesn’t do much of anything—it just tells us whether a given string can be recognized or not. / s i m p l e p a r s e r " user>> . Note that since we defined spaces in terms of skipMany1. representing the Scheme form (a b . In this example. / s i m p l e p a r s e r % No match : " lisp " ( l i n e 1 . Generally.8 CHAPTER 2. hs %" Found v a l u e column 1 ) : abc " column 4 ) : [edit section] Return Values Right now. A DottedList. we need to define a data type that can hold any Lisp value: 21 22 23 24 25 26 data L i s p V a l = | | | | | Atom String Li st [ L i s p V a l ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool This is an example of an algebraic data type: it defines a set of possible values that a variable of type LispVal can hold. which stores a list of other LispVals (Haskell lists are denoted by brackets). A List. containing a Haskell Integer . / s i m p l e p a r s e r " No match : " lisp " ( l i n e 1 . it will no longer recognize a plain old single character. An Atom. This stores a list of all elements but the last.

containing a Haskell boolean value 9 Constructors and types have different namespaces. so you can have both a constructor named String and a type named String. In this respect. This is because we’ll be retrieving the value of our parse (returned by many (noneOf "¨" ) ) and manipulating it. Return lets us wrap that up in a Parser action that consumes no input but returns it as the inner value. Once we’ve finished the parse and have the Haskell String returned from many. >>= if you’ll be immediately passing that value into the next action. Now let’s move on to Scheme variables. followed by any number of letters. Both types and constructor tags always begin with capital letters. followed by a closing quote mark: 28 29 30 31 32 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " ###BOT_TEXT###quot; " ) c h a r ’"’ return $ String x We’re back to using the do-notation instead of the >> operator. partially apply it. and do-notation otherwise. containing a Haskell String 6. It also serves as a pattern that can be used in the left-hand side of a pattern-matching expression. In general. digits. Every constructor in an algebraic data type also acts like a function that turns its arguments into a value of its type. the whole parseString action will have type Parser LispVal. We then apply the built-in function return to lift our LispVal into the Parser monad. interleaving some other parse operations in the meantime. etc. each line of a do-block must have the same type. A string is a double quote mark.RETURN VALUES 5. let’s add a few more parsing functions to create values of these types. we apply the String constructor (from our LispVal data type) to turn it into a LispVal. use >> if the actions don’t return a value. but $ is right-associative. The $ operator is infix function application: it’s the same as if we’d written return (String x). it functions like the Lisp function apply. A Bool. letting us eliminate some parentheses. An atom is a letter or symbol. Remember. followed by any number of non-quote characters. Next. you can do anything with it that you’d normally do to a function: pass it around. but the result of our String constructor is just a plain old LispVal. A String. Thus. or symbols: 34 35 36 37 38 39 40 41 parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom of "#t" −> Bool True "#f" −> Bool False otherwise −> Atom atom . Since $ is an operator.

Then we pass the result to Number to get a LispVal. Unfortunately. We’ll be seeing many more examples throughout the rest of the tutorial. breaking down intermediate steps into other functions that can be combined in various ways. we create one more parser. we introduce another Parsec combinator. then it returns the value returned by that parser. tries the second. so here we’re matching one or more digits. it means that you often have to read Haskell code from right-to-left and keep careful track of the types. function application. so we apply liftM to our Number . PARSING Here. we use the built-in function read to convert that string into a number. giving us back a Parser LispVal. It often lets you express very complicated algorithms in a single line. read function. we need to put them together. we need only separate them by commas. and then apply the result of that to our parser. Unfortunately. We use the list concatenation operator ++ for this. We also have to import the Monad module up at the top of our program to get access to liftM: 2 import Monad This style of programming—relying heavily on function composition. or an atom: . We’d like to construct a number LispVal from the resulting string.) associate to the right. First. The parsec combinator many1 matches one or more of its argument. The let statement defines a new variable atom. a number. Let’s create a parser that accepts either a string. so we use that to combine the two function applications. so we convert it into a singleton list by putting brackets around it. matching against the literal strings for true and false.10 CHAPTER 2. If we’d wanted to create a list containing many elements. and then always returns the value of atom Finally. The standard function liftM does exactly that. but we have a few type mismatches. We need a way to tell it to just operate on the value inside the monad. then if it fails. the result of many1 digit is actually a Parser String. so our combined Number . The otherwise alternative is a readability trick: it binds a variable named otherwise. read ) $ many1 d i g i t It’s easiest to read this backwards. for numbers. creates a function that applies its right argument and then passes the result to the left argument. Then we use a case statement to determine which LispVal to create and return. Recall that first is just a single character. since both function application ($) and function composition (. The first parser must fail before it consumes any input: we’ll see later how to implement backtracking. Once we’ve read the first character and the rest of the atom. If either succeeds. the choice operator <|>. This tries the first parser. so hopefully you’ll get pretty comfortable with it. read still can’t operate on it. This shows one more way of dealing with monadic values: 43 44 parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . whose value we ignore. and passing functions to functions—is very common in Haskell code. The function composition operator .

/ : < = ? > @ ^ _ ~ # " readExpr : : String −> String readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> " N o m a t c h : " ++ show e r r Right v a l −> " F o u n d v a l u e " spaces : : Parser () s p a c e s = skipMany1 s p a c e 15 20 25 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 30 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . read ) $ many1 d i g i t 35 40 45 parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber .hs) module import import import 5 Main where Monad System . P a r s e c hiding ( s p a c e s ) main : : IO ( ) main = do a r g s <− getArgs putStrLn ( readExpr ( a r g s 10 !! 0) ) symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + .RETURN VALUES 45 46 47 48 11 parseExpr : : P a r s e r L i s p V a l parseExpr = parseAtom <|> p a r s e S t r i n g And edit readExpr so it calls our new parser: 1 2 3 4 readExpr : : String −> String readExpr i n p u t = case p a r s e parseExpr " lisp " i n p u t of Left e r r −> " No match : " ++ show e r r Right −> " Found value " The complete code is therefore: Listing 2. Environment Text .3: A parser able to handle data types (datatypeparser. P a r s e r C o m b i n a t o r s .

or symbol.12 CHAPTER 2. Our strings aren’t quite R5RS compliant. hs user>> . Rewrite parseNumber using (a) do-notation. string. and any other desired escape characters. PARSING Compile and run this code. but not other strings: user>> ghc − package p a r s e c −o s i m p l e p a r s e r d a t a t y p e p a r s e r . column 1 ) : u n e x p e c t e d "(" e x p e c t i n g l e t t e r . / s i m p l e p a r s e r 25 Found v a l u e user>> . Change parseNumber to support the Scheme standard for different bases. / s i m p l e p a r s e r "( symbol )" No match : " lisp " ( l i n e 1 . The Haskell function readFloat may be useful. or a Complex as a real and imaginary part (each itself a Real number). \r. / s i m p l e p a r s e r " ###BOT_TEXT###quot; this is a string ###BOT_TEXT###quot; " Found v a l u e user>> . Add a Float constructor to LispVal. 5. (b) explicit sequencing with the >>= operator. Change parseString so that ###BOT_TEXT###quot; gives a literal quote character instead of terminating the string. a Rational as a numerator and denominator. 2. For the others. Add a Character constructor to LispVal. and create a parser for character literals as described in R5RS. . \t. You may want to replace noneOf "###BOT_TEXT###quot;"with a new parser action that accepts either a non-quote character or a backslash followed by a quote mark. you can define compound types that represent eg. " ###BOT_TEXT###quot; " o r d i g i t Exercises 1. 3. 7. 6. \. 4. and you’ll notice that it accepts any number. / s i m p l e p a r s e r symbol Found v a l u e user>> . You may find the readOct and readHex functions useful. Add data types and parsers to support the full numeric tower of Scheme numeric types. Modify the previous exercise to support \n. because they don’t support escaping of internal quotes within the string. check the Prelude. and support R5RS syntax for decimals. / s i m p l e p a r s e r ( symbol ) bash : s y n t a x e r r o r n e a r u n e x p e c te d tok en ‘ symbol ’ user>> . Haskell has built-in types to represent many of these.

we add a few more parser actions to our interpreter. Note too that we can pass parseExpr to sepBy. AND QUOTED DATUMS13 Recursive Parsers: Adding lists. but still uses only concepts that we’re already familiar with: 49 50 51 52 53 parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy parseExpr s p a c e s t a i l <− c h a r ’ . exactly the type we need for the do-block. let’s add support for the single-quote syntactic sugar of Scheme: 55 56 57 58 59 parseQuoted : : P a r s e r L i s p V a l parseQuoted = do char ’ \ ’ ’ x <− parseExpr return $ Li st [ Atom " quote " . and quoted datums Next. The expression char ’. dotted lists. Start with the parenthesized lists that make Lisp famous: 46 47 [edit section] p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM Li s t $ sepBy parseExpr s p a c e s This works analogously to parseNumber.’ >> spaces returns a Parser (). like put it in a list. x ] Most of this is fairly familiar stuff: it reads a single quote character. then combining that with parseExpr gives a Parser LispVal. reads an expression and binds it to x.(try parseList) <|> parseDottedList char ’)’ return x . The Atom constructor works like an ordinary function: you pass it the String you’re encapsulating. and then returns (quote x). and it gives you back a LispVal.RECURSIVE PARSERS: ADDING LISTS. DOTTED LISTS. first parsing a series of expressions separated by whitespace (sepBy parseExpr spaces) and then apply the List constructor to it within the Parser monad. to use Scheme notation. even though it’s an action we wrote ourselves. You can do anything with this LispVal that you normally could. Next. ’ >> s p a c e s >> parseExpr return $ D o t t e d L i s t head t a i l Note how we can sequence together a series of Parser actions with >> and then use the whole sequence on the right hand side of a do-statement. The dotted-list parser is somewhat more complex. edit our definition of parseExpr to include our new parsers: 1 2 3 4 5 6 7 8 9 parseExpr : : P a r s e r L i s p V a l parseExpr = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> parseQuoted <|> do char ’(’ x <. Finally.

The complete code is therefore: Listing 2./ : < = ? > @ ^ _ ~ # " readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> " N o m a t c h : " ++ show e r r Right v a l −> " F o u n d v a l " spaces : : Parser () s p a c e s = skipMany1 s p a c e 15 20 25 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 30 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . The try combinator attempts to run the specified parser. This lets you use it in a choice alternative without interfering with the other alternative. parseList and parseDottedList recognize identical strings up to the dot. PARSING This illustrates one last feature of Parsec: backtracking. dotted list and quoted datum parsing (recursiveparser.4: A simpe parser. P a r s e r C o m b i n a t o r s . ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l . this breaks the requirement that a choice alternative may not consume any input before failing. Environment Text . read ) $ many1 d i g i t 35 40 45 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s 50 parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ . now with Lisp list.hs) module import import import 5 Main where Monad System . P a r s e c hiding ( s p a c e s ) main : : IO ( ) main = do a r g s <− getArgs putStrLn ( readExpr ( a r g s 10 !! 0) ) symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . it backs up to the previous state. but if it fails.14 CHAPTER 2.

3. / s i m p l e p a r s e r "(a Found v a l u e user>> . later in this tutorial. You should end up with a parser that matches a string of expressions. AND QUOTED DATUMS15 55 parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . That’s the power of recursion. . Instead of using the try combinator. Combining the return values of these into either a List or a DottedList is left as a (somewhat tricky) exercise for the reader: you may want to break it out into another helper function. The Haskell representation is up to you: GHC does have an Array data type. Exercises 1. / s i m p l e p a r s e r "(a No match : " lisp " ( l i n e 1 . list )) test )" ’( imbalanced parens )" column 2 4 ) : Note that by referring to parseExpr within our parsers. list ) test )" ’( quoted ( dotted .RECURSIVE PARSERS: ADDING LISTS. You may have a better idea how to do this after the section on set!. left-factor the grammar so that the common subsequence is its own parser. u n e x p e c t e d end o f i n p u t e x p e c t i n g s p a c e o r ")" −o s i m p l e p a r s e r r e c u r s i v e p a r s e r . / s i m p l e p a r s e r "(a Found v a l u e user>> . 2. / s i m p l e p a r s e r "(a Found v a l u e user>> . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x 60 65 Compile and run this code: user>> ghc − package p a r s e c user>> . Add support for the backquote syntactic sugar: the Scheme standard details what it should expand into (quasiquote/unquote). Thus. / s i m p l e p a r s e r "(a Found v a l u e user>> . a vector should have constant-time indexing and updating. but it can be difficult to use. we can nest them arbitrarily deep. we get a full Lisp reader with only a few definitions. Add support for vectors. DOTTED LISTS. hs test )" ( nested ) test )" ( dotted . but destructive update in a purely functional language is difficult. and one that matches either nothing or a dot and a single expressions. Strictly speaking.

PARSING .16 CHAPTER 2.

The List and DottedList clauses work similarly. but fairly soon you’ll be progressing to doing working computations. you only need to know that each clause of the above definition matches one of the constructors of LispVal. We’re about to take the first steps towards a working Scheme interpreter: assigning values to program fragments. Any constructor can appear in a pattern. and the right-hand side tells what to do for a value of that constructor. The clauses of a function definition are tried in textual order. until one of the patterns matches. but we need to define a helper function unwordsList to convert the contained list into a string: 76 showVal ( Li st c o n t e n t s ) = "(" ++ u n w o r d s L i s t c o n t e n t s ++ ")" 17 . that pattern matches a value if the tag is the same as the value’s tag and all subpatterns match their corresponding components. If this is confusing. For now. We’ll be starting with baby steps.Chapter 3 Evaluation. you’ll see some examples of deeply-nested patterns when we get further into the evaluator. Let’s start by telling Haskell how to print out a string representation of the various possible LispVals: 70 71 72 73 74 75 • live version • discussion • edit • comment • report an error [edit section] showVal showVal showVal showVal showVal showVal : : L i s p V a l −> String ( String c o n t e n t s ) = " ###BOT_TEXT###quot; " ++ c o n t e n t s ++ " ###BOT_TEXT###quot; " (Atom name ) = name ( Number c o n t e n t s ) = show c o n t e n t s ( Bool True ) = "#t" ( Bool False ) = "#f" This is our first real introduction to pattern matching. selecting a code clause based on its constructor and then binding the components to variables. Part 1 Beginning the Evaluator Currently. with matching proceeding in an inside −> outside. left −> right order. we’ve just been printing out whether or not we recognize the given program fragment. Pattern matching is a way of destructuring an algebraic data type. Patterns can be nested arbitrarily deep.

Since we’re dealing with a list of LispVals instead of words.” Instead. Haskell functions are curried: this means that a function of two arguments. without regard to individual values or “points.hs) module import import import 5 Main where Monad System . we compose it with unwords: map showVal converts a list of LispVals to a list of their String representations.1: A parser using pattern matching (evaluator1. This standard Haskell function lets you convert any type that’s an instance of the class Show into a string. so we make it into a member of the class Show. instead of just "Found value ": 13 14 15 readExpr i n p u t = case p a r s e parseExpr " lisp " i n p u t of Left e r r −> " No match : " ++ show e r r Right v a l −> " Found " ++ show v a l The complete code is therefore: Listing 3. we partially-apply map to showVal. Let’s try things out by changing our readExpr function so it returns the string representation of the value actually parsed. Environment Text . if you supply only a single argument. which creates a function that takes a list of LispVals and returns a list of their string representations. we define it as the composition of a couple built-in functions. First. EVALUATION. which glues together a list of words with spaces. defining its show method as showVal: 82 instance Show L i s p V a l where show = showVal A full treatment of typeclasses is beyond the scope of this tutorial. and apply later. compose. is really a function that returns a function of one argument. we define a function that first converts the LispVals into their string representations and then applies unwords to it: 79 80 u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords .18 77 CHAPTER 3. PART 1 showVal ( D o t t e d L i s t head t a i l ) = "(" ++ u n w o r d s L i s t head ++ " . P a r s e r C o m b i n a t o r s . and then unwords joins the result together with spaces. We’d like to be able to do the same with LispVal. As a result. map showVal Our definition of unwordsList doesn’t include any arguments. you get back a function one argument that you can pass around. This is an example of point-free style: writing definitions purely in terms of function composition and partial application. In this case. like map. " ++ showVal t a i l ++ ")" The unwordsList function works like the Haskell Prelude’s unwords function. P a r s e c hiding ( s p a c e s ) main : : IO ( ) main = do a r g s <− getArgs putStrLn ( readExpr ( a r g s 10 !! 0) ) symbol :: P a r s e r Char . you can find more information in other tutorials and the Haskell 98 report. We used the function show above.

" ++ 50 55 60 65 70 75 . ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . read ) $ many1 d i g i t 35 40 45 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ ./ : < = ? > @ ^ _ ~ # " readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> " N o m a t c h : " ++ show e r r Right v a l −> " F o u n d " ++ show v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e 20 19 15 25 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 30 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal : : L i s p V a l −> String showVal ( String c o n t e n t s ) = " \ " " showVal ( Atom name ) = name showVal ( Number c o n t e n t s ) = show showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ showVal ( D o t t e d L i s t head t a i l ) = showVal t a i l ++ " ) " ++ c o n t e n t s ++ " \ " " contents u n w o r d s L i s t c o n t e n t s ++ " ) " " ( " ++ u n w o r d s L i s t head ++ " .BEGINNING THE EVALUATOR symbol = oneOf " ! $ % & | * + .

We match that against the specific two-element list [Atom "quote". and quoted lists is fairly simple: return the datum itself. Evaluating numbers. 85 86 87 88 89 eval eval eval eval eval : : L i s p V a l −> L i s p V a l val@ ( String ) = v a l val@ ( Number ) = v a l val@ ( Bool ) = v a l ( Li s t [ Atom " quote " . Let’s integrate eval into our existing code.. a list where the first element is the symbol "quote" and the second element can be anything. The notation val@(String ) matches against any LispVal that’s a string and then binds val to the whole LispVal.20 CHAPTER 3. we start with the beginnings of an evaluator. / p a r s e r " (1 2 2) " (1 2 2) . The result has type LispVal instead of type String. In Lisp. The purpose of an evaluator is to map some “code” data type into some “data” data type. strings. with a variety of syntactic forms. but is most useful with @-patterns (where you bind the variable to the whole pattern) and with simple constructor-tests where you’re just interested in the tag of the constructor. v a l ] ) = v a l This introduces a new type of pattern. Start by changing readExpr back so it returns the expression instead of a string representation of the expression: 12 13 14 15 readExpr : : String −> L i s p V a l readExpr i n p u t = case p a r s e parseExpr " lisp " i n p u t of Left e r r −> String $ " No match : " ++ show e r r Right v a l −> v a l . so our evaluator will return a LispVal. Then we return that second element. the data types for both code and data are the same. matching any value yet not binding it to a variable. and not just the contents of the String constructor. hs . The underbar is the “don’t care” variable. The type of data contained by List is [LispVal]. user>> user>> Found user>> Found ghc − package p a r s e c −o p a r s e r e v a l u a t o r 1 . map showVal instance Show L i s p V a l where show = showVal And compile and run. EVALUATION. a list of LispVals. The last clause is our first introduction to nested patterns. It can be used in any pattern. booleans. PART 1 80 u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords .. the result of the evaluation. val]. / p a r s e r " ’(1 3 (###BOT_TEXT###quot; this ###BOT_TEXT###quot; ###BOT_TEXT###quot; one ###BOT_TEXT###quot;) )" ( qu o t e ( 1 3 ( " this " " one " ) ) ) Beginnings of an Evaluator: Primitives [edit section] Now. Other languages often have more complicated code structures.

readExpr . we take the result of the getArgs action (a list of strings) and pass it into the composition of: 1. 2.2: The evaluator skeleton (evaluator2. Now that we know about the >>= monad sequencing operator and the function composition operator.hs) module import import import 5 Main where Monad System . ( ! ! 0) Here. P a r s e c hiding ( s p a c e s ) eval . readExpr .BEGINNINGS OF AN EVALUATOR: PRIMITIVES 21 And then change our main function to read an expression. Parse it (readExpr) 3. convert it to a string. e v a l ./ : < = ? > @ ^ _ ~ # " readExpr : : String −> L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> String $ " N o m a t c h : " ++ show e r r Right v a l −> v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e 15 20 25 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 30 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x . Take the first value ( (!! 0)). Convert it to a string (show) 5. giving you back a function that takes the first element of whatever list it’s passed. (!! 0) main : : IO ( ) main = getArgs > >= putStrLn . evaluate it. This notation is known as an operator section: it’s telling the compiler to partially-apply the list indexing operator to 0. P a r s e r C o m b i n a t o r s . let’s use them to make this a bit more concise: 6 7 main : : IO ( ) main = getArgs >>= putStrLn . show . and print it out. Evaluate it (eval) 4. Print it (putStrLn) The complete code is therefore: Listing 3. Environment Text . 10 symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . show .

22 CHAPTER 3. PART 1 35 40 parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . hs . EVALUATION. v a l ] ) = v a l Compile and run the code the normal way: user>> user>> atom user>> 2 user>> ghc − package p a r s e c −o e v a l e v a l u a t o r 2 . / e v a l " ’ atom " . " ++ 55 60 65 70 75 80 u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . map showVal instance Show L i s p V a l where show = showVal 85 eval eval eval eval eval : : L i s p V a l −> L i s p V a l val@ ( String ) = val val@ ( Number ) = val val@ ( Bool ) = val ( L i s t [ Atom " q u o t e " ./ eval 2 . / e v a l " ###BOT_TEXT###quot; a string ###BOT_TEXT###quot; " . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal : : L i s p V a l −> String showVal ( String c o n t e n t s ) = " \ " " showVal ( Atom name ) = name showVal ( Number c o n t e n t s ) = show showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ showVal ( D o t t e d L i s t head t a i l ) = showVal t a i l ++ " ) " ++ c o n t e n t s ++ " \ " " contents u n w o r d s L i s t c o n t e n t s ++ " ) " " ( " ++ u n w o r d s L i s t head ++ " . read ) $ many1 d i g i t 45 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s 50 parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ .

( "*" . Lists in Haskell are really syntactic sugar for a change of cons applications and the empty list: [1. numericBinop ( − ) ) . and pass it and the original function to apply: 91 92 ap p l y : : String −> [ L i s p V a l ] −> L i s p V a l ap p l y f u n c a r g s = maybe ( Bool False ) ( $ a r g s ) $ lookup f u n c primitives The built-in function lookup looks up a key (its first argument) in a list of pairs. lookup will fail if no pair in the list contains the matching key. we apply it to the arguments using ($ args). Remember that all clauses of a function definition must be placed together and are evaluated in textual order. we’re saying “give me the rest of the list” instead of “give me the second element of the list.ADDING BASIC PRIMITIVES "a string " user>> . It’s still not yet a “programming language”. Begin by adding a clause to eval to handle function application. 2. This is what lets us write compound expressions like (+ 2 (− 3 1) (∗ 5 4)) . We use the function maybe to specify what to do in case of either success or failure. We have to recursively evaluate each argument. If it is found. Adding Basic Primitives Next. If the function isn’t found. we return a Bool False value. Next. 4] = 1:(2:(3:(4:[]) ) ) . we define the list of primitives that we support: 94 95 96 97 p r i m i t i v e s : : [ ( String . The rest of the clause consists of a couple functions we’ve seen before and one we haven’t defined yet. func would be bound to + and args would be bound to [Number 2. numericBinop ( ∗ ) ) . numericBinop (+) ) . To express this. Then we take the resulting list of evaluated arguments. it returns an instance of the built-in type Maybe. we’ll be extending it with We still can’t do all that much useful with the program (witness the failed some functions to make it useful. Number 2]. 3. but it’s getting close. an operator section of the function application operator. so we map eval over the args. we’ll improve our Scheme so we can use it as a simple calculator. However. / e v a l " (+ 2 2) " F a i l : e v a l . if we passed (+ 2 2) to eval. hs : 8 3 : Non− e x h a u s t i v e p a t t e r n s i n f u n c t i o n e v a l 23 (+ 2 2) call). ( " -" .” For example. Soon. but this time we match against the cons operator : instead of a literal list. so this should go after the other eval clauses: 89 [edit section] e v a l ( Li st (Atom f u n c : a r g s ) ) = a pply f u n c $ map e v a l a r g s This is another nested pattern. equivalent to #f (we’ll add more robust error-checking later). By pattern-matching against cons itself instead of a literal list. [ L i s p V a l ] −> L i s p V a l ) ] p r i m i t i v e s = [ ( "+" . but the basic skeleton is in place. .

numericBinop rem ) ] Look at the type of primitives. Our numeric operations can work on a list of any length. attempt to parse it with Haskell’s built-in reads function. numericBinop quot ) . It is a list of pairs. ( " mod " . we’ll use it as one.hs) module Main where import Monad . the functions that we store are themselves the result of a function. we’re implementing a form of weak typing. apply the function to it. we’ll return 0 for now. so (+ 2 3 4) = 2 + 3 + 4. original value). PART 1 ( "/" . In Haskell. just like lookup expects. We use the built-in function foldl1 to do this. Also. and (− 15 5 3 2) = 15 − 5 − 3 − 2. even if it’s tagged as a string. That means that if a value can be interpreted as a number (like the string "2"). which we haven’t defined yet.3: A basic evaluator (evaluator3. If we can’t parse the number. for any reason. This takes a primitive Haskell function (often an operator section) and wraps it with code to unpack an argument list. For lists. numericBinop div ) . EVALUATION. The complete code is therefore: Listing 3. We’ll fix this shortly so that it signals an error. though the functions must all have the same type. numericBinop mod) . Unlike R5RS Scheme. we pattern-match against the one-element list and try to unpack that. ( " quotient " . It essentially changes every cons operator in the list to the binary function we supply. We do this by adding a couple extra clauses to unpackNum. but the values of the pairs are functions from [LispVal] to LispVal. which returns a list of pairs of (parsed value. and wrap the result up in our Number constructor. If we’re unpacking a string. ( " remainder " . Anything else falls through to the next case.24 98 99 100 101 CHAPTER 3. 103 104 numericBinop : : ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> L i s p V a l numericBinop op params = Number $ f o l d l 1 op $ map unpackNum params unpackNum : : L i s p V a l −> Integer unpackNum ( Number n ) = n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null p a r s e d then 0 else fst $ parsed ! ! 0 unpackNum ( Li st [ n ] ) = unpackNum n unpackNum = 0 105 106 107 108 109 110 111 112 113 As with R5RS Scheme. you can easily store functions in other data structures. we don’t limit ourselves to only two arguments. op. numericBinop.

(!! 0) symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . 10 eval . ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . P a r s e r C o m b i n a t o r s . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal : : L i s p V a l −> String 50 55 60 65 70 . readExpr .ADDING BASIC PRIMITIVES import System . show . read ) $ many1 d i g i t 35 40 45 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ . Environment import Text ./ : < = ? > @ ^ _ ~ " readExpr : : String −> L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> String $ " N o m a t c h : " ++ show e r r Right v a l −> v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e 15 20 25 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 30 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . P a r s e c hiding ( s p a c e s ) 5 25 main : : IO ( ) main = getArgs > >= putStrLn .

numericBinop ( − ) ) . / e v a l " (+ 2 (.(+ 4 6 3) 3 5 2) " . " ++ 75 80 u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . ( " r e m a i n d e r " . PART 1 ++ c o n t e n t s ++ " \ " " contents u n w o r d s L i s t c o n t e n t s ++ " ) " " ( " ++ u n w o r d s L i s t head ++ " . EVALUATION. / e v a l "(.26 showVal ( String c o n t e n t s ) = " \ " " showVal ( Atom name ) = name showVal ( Number c o n t e n t s ) = show showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ showVal ( D o t t e d L i s t head t a i l ) = showVal t a i l ++ " ) " CHAPTER 3. [ L i s p V a l ] −> L i s p V a l ) ] p r i m i t i v e s = [ ( " + " ." . ( " * " . ( " . / e v a l " (+ 2 ( -4 1) )" . hs . numericBinop (+) ) . numericBinop rem ) ] numericBinop : : ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> L i s p V a l numericBinop op params = Number $ f o l d l 1 op $ map unpackNum params 105 110 unpackNum : : L i s p V a l −> Integer unpackNum ( Number n ) = n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null parsed then 0 else fst $ parsed ! ! 0 unpackNum ( L i s t [ n ] ) = unpackNum n unpackNum = 0 Compile and run this the normal way. numericBinop quot ) . ( " q u o t i e n t " . v a l ] ) = v a l ( L i s t ( Atom f u n c : a r g s ) ) = a p p l y f u n c $ map e v a l a r g s 90 a p p l y : : String −> [ L i s p V a l ] −> L i s p V a l a p p l y f u n c a r g s = maybe ( Bool False ) ( $ a r g s ) $ lookup f u n c p r i m i t i v e s 95 100 p r i m i t i v e s : : [ ( String . Note how we get nested expressions “for free” because we call eval on each of the arguments of a function: user>> user>> 4 user>> 2 user>> 5 user>> 3 ghc − package p a r s e c −o e v a l neweval . numericBinop div ) . map showVal instance Show L i s p V a l where show = showVal 85 eval eval eval eval eval eval : : L i s p V a l −> L i s p V a l val@ ( String ) = val val@ ( Number ) = val val@ ( Bool ) = val ( L i s t [ Atom " q u o t e " . ( " / " . / e v a l " (+ 2 2) " . numericBinop ( ∗ ) ) . ( " m o d " . numericBinop mod) .4 1) )" .

Change unpackNum so that it always returns 0 if the value is not a number. even if it’s a string or list that could be parsed as a number. Add the symbol-handling functions from R5RS. 3. A symbol is what we’ve been calling an Atom in our data constructors. Add primitives to perform the various type-testing functions of R5RS: symbol?. .EXERCISES 27 Exercises 1. string?. number?. etc. 2.

EVALUATION. PART 1 .28 CHAPTER 3.

we need to import Control. it often means that errors pass silently throughout the program until they become big problems. Some languages . but we might as well forsee all the other things that can go wrong in the interpreter later. We’d like to signal errors as soon as they happen and immediately break out of execution. there are a variety of places within the code where we either ignore errors or silently assign “default” values like \#f or 0 that make no sense. Next. However.Monad.Chapter 4 Error Checking and Exceptions Currently. E r r o r Then. Monad .get along fine with this approach.like Perl and PHP . First. which means rather inconvenient debugging sessions for the programmer.Error to get access to Haskell’s built-in error functions: 4 • live version • discussion • edit • comment • report an error import C o n t r o l . we define how to print out the various types of errors and make LispError an instance of Show: 131 132 133 showError : : L i s p E r r o r −> String showError ( UnboundVar message varname ) = message ++ ": " ++ varname showError ( BadSpecialForm message form ) = message ++ ": " ++ show form 29 . we should define a data type to represent an error: 123 124 125 126 127 128 129 data L i s p E r r o r = | | | | | | NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l NotFunction String String UnboundVar String String D e f a u l t String This is a few more constructors than we need at the moment.

30
134

CHAPTER 4. ERROR CHECKING AND EXCEPTIONS

135

136

137

138

139

showError ( NotFunction message f u n c ) = message ++ ": " ++ show func showError ( NumArgs e x p e c t e d found ) = " Expected " ++ show expected ++ " args : found values " ++ u n w o r d s L i s t found showError ( TypeMismatch e x p e c t e d found ) = " Invalid type : expected " ++ e x p e c t e d ++ " , found " ++ show found showError ( P a r s e r p a r s e E r r ) = " Parse error at " ++ show parseErr instance Show L i s p E r r o r where show = showError

140 141

Our next step is to make our error type into an instance of Error. This is necessary for it to work with GHC’s built-in error handling functions. Being an instance of error just means that it must provide functions to create an instance either from a previous error message or by itself:
143 144 145

instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " An error has occurred " strMsg = D e f a u l t

Then we define a type to represent functions that may throw a LispError or return a value. Remember how parse used an Either data type to represent exceptions? We take the same approach here:
147

type ThrowsError = Either L i s p E r r o r

Type constructors are curried just like functions, and can also be partially applied. A full type would be Either LispError Integer or Either LispError LispVal, but we want to say ThrowsError LispVal and so on. We only partially apply Either to LispError, creating a type constructor ThrowsError that we can use on any data type. Either is yet another instance of a monad. In this case, the “extra information” being passed between Either actions is whether or not an error occurred. Bind applies its function if the Either action holds a normal value, or passes an error straight through without computation. This is how exceptions work in other languages, but because Haskell is lazily-evaluated, there’s no need for a separate control-flow construct. If bind determines that a value is already an error, the function is never called. The Either monad also provides two other functions besides the standard monadic ones: 1. throwError, which takes an Error value and lifts it into the Left (error) constructor of an Either 2. catchError, which takes an Either action and a function that turns an error into another Either action. If the action represents an error, it

31 applies the function, which you can use to eg. turn the error value into a normal one via return or re-throw as a different error. In our program, we’ll be converting all of our errors to their string representations and returning that as a normal value. Let’s create a helper function to do that for us:
149

t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . show )

The result of calling trapError is another Either action which will always have valid (Right) data. We still need to extract that data from the Either monad so it can passed around to other functions:
151 152

e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l

We purposely leave extractValue undefined for a Left constructor, because that represents a programmer error. We intend to use extractValue only after a catchError, so it’s better to fail fast than to inject bad values into the rest of the program. Now that we have all the basic infrastructure, it’s time to start using our error-handling functions. Remember how our parser had previously just return a string saying "No match" on an error? Let’s change it so that it wraps and throws the original ParseError:
16 17 18 19

readExpr : : String −> ThrowsError L i s p V a l readExpr i n p u t = case p a r s e parseExpr " lisp " i n p u t of Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l

Here, we first wrap the original ParseError with the LispError constructor Parser, and then use the built-in function throwError to return that in our ThrowsError monad. Since readExpr now returns a monadic value, we also need to wrap the other case in a return function. Next, we change the type signature of eval to return a monadic value, adjust the return values accordingly, and add a clause to throw an error if we encounter a pattern that we don’t recognize:
88 89 90 91 92 93 94

e v a l : : L i s p V a l −> ThrowsError L i s p V a l e v a l val@ ( String ) = return v a l e v a l val@ ( Number ) = return v a l e v a l val@ ( Bool ) = return v a l e v a l ( Li st [ Atom " quote " , v a l ] ) = return v a l e v a l ( Li st (Atom f u n c : a r g s ) ) = mapM e v a l a r g s >>= a pply f u n c e v a l badForm = t h r o w E r r o r $ BadSpecialForm " Unrecognized special form " badForm

Since the function application clause calls eval (which now returns a monadic value) recursively, we need to change that clause. First, we had to change map to mapM, which maps a monadic function over a list of values, sequences the resulting actions together with bind, and then returns a list of the inner results.

32

CHAPTER 4. ERROR CHECKING AND EXCEPTIONS

Inside the Error monad, this sequencing performs all computations sequentially but throws an error value if any one of them fails—giving you Right [results] on success, or Left error on failure. Then, we used the monadic bind operation to pass the result into the partially applied apply func, again returning an error if either operation failed. Next, we change apply itself so that it throws an error if it doesn’t recognize the function:
96 97

98 99

a p p l y : : String −> [ L i s p V a l ] −> ThrowsError L i s p V a l a p p l y f u n c a r g s = maybe ( t h r o w E r r o r $ NotFunction " Unrecognized primitive function args " f u n c ) ($ args ) ( lookup f u n c p r i m i t i v e s )

We didn’t add a return statement to the function application ($ args). We’re about to change the type of our primitives, so that the function returned from the lookup itself returns a ThrowsError action:
101

primitives : :

[ ( String , [ L i s p V a l ] −> ThrowsError L i s p V a l ) ]

And, of course, we need to change the numericBinop function that implements these primitives so it throws an error if there’s only one argument:
110

111

112

numericBinop : : ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l numericBinop op s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 singleVal numericBinop op params = mapM unpackNum params >>= return . Number . f o l d l 1 op

We use an at-pattern to capture the single-value case because we want to include the actual value passed in for error-reporting purposes. Here, we’re looking for a list of exactly one element, and we don’t care what that element is. We also need to use mapM to sequence the results of unpackNum, because each individual call to unpackNum may fail with a TypeMismatch:
114 115 116 117 118

119 120 121

unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null p a r s e d then t h r o w E r r o r $ TypeMismatch " number " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( Li st [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " number " notNum

Finally, we need to change our main function to use this whole big error monad. This can get a little complicated, because now we’re dealing with two monads (Error and IO). As a result, we go back to do-notation, because it’s nearly impossible to use point-free style when the result of one monad is nested inside another:

evaled is the result of: (a) taking first argument (args !! 0) (b) parsing it (readExpr) (c) passing it to eval (>>= eval./ : < = ? > @ ^ _ ~ # " !! 0) > >= e v a l 15 readExpr : : String −> ThrowsError L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l . Monad . because our trapError function can only convert errors to strings.33 7 8 9 10 11 main : : IO ( ) main = do a r g s <− getArgs e v a l e d <− return $ liftM show $ readExpr ( a r g s ! ! 0 ) >>= eval putStrLn $ e x t r a c t V a l u e $ t r a p E r r o r e v a l e d Here’s what this new function is doing: 1. converting errors to their string representation. E r r o r Text . P a r s e c hiding ( s p a c e s ) 5 10 main : : IO ( ) main = do a r g s <− getArgs e v a l e d <− return $ liftM show $ readExpr ( a r g s putStrLn $ e x t r a c t V a l u e $ t r a p E r r o r e v a l e d symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . It has to be. giving evaled type Either LispError String.1: Lisp parser with basic error-checking functionality (errorcheck. args is the list of command-line arguments 2. P a r s e r C o m b i n a t o r s . and that type must match the type of normal values 3. Environment C o n t r o l . the bind operation has higher precedence than function application) (d) calling show on it within the Error monad.hs) module import import import import Main where Monad System . (b) calling extractValue to get a String out of this Either LispError String action (c) printing the results through putStrLn The complete code is therefore: Listing 4. Note also that the whole action has type IO (Either LispError String). caught is the result of (a) calling trapError on evaled.

" ++ 55 60 65 70 75 80 u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . ERROR CHECKING AND EXCEPTIONS spaces : : Parser () s p a c e s = skipMany1 s p a c e 25 30 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 35 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . read ) $ many1 d i g i t 40 45 50 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal : : L i s p V a l −> String showVal ( String c o n t e n t s ) = " \ " " showVal ( Atom name ) = name showVal ( Number c o n t e n t s ) = show showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ showVal ( D o t t e d L i s t head t a i l ) = showVal t a i l ++ " ) " ++ c o n t e n t s ++ " \ " " contents u n w o r d s L i s t c o n t e n t s ++ " ) " " ( " ++ u n w o r d s L i s t head ++ " .34 20 CHAPTER 4. ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . map showVal 85 instance Show L i s p V a l where show = showVal .

Number . ( " q u o t i e n t " . f o u n d " ++ show f o u nd s h o w E r r o r ( P a r s e r p a r s e E r r ) = " P a r s e e r r o r a t " ++ show p a r s e E r r instance Show L i s p E r r o r where show = s h o w E r r o r instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " A n e r r o r h a s o c c u r r e d " st r M sg = D e f a u l t type ThrowsError = Either L i s p E r r o r 140 145 . ( " . [ L i s p V a l ] −> ThrowsError L i s p V a l ) ] p r i m i t i v e s = [ ( " + " . ( " r e m a i n d e r " . numericBinop (+) ) . numericBinop ( − ) ) . v a l ] ) = return v a l ( L i s t ( Atom f u n c : a r g s ) ) = mapM e v a l a r g s > >= a p p l y f u n c badForm = t h r o w E r r o r $ BadSpecialForm " U n r e c o g n i z e d s p e c i a l f o r m " badForm 95 a p p l y : : String −> [ L i s p V a l ] −> ThrowsError L i s p V a l a p p l y f u n c a r g s = maybe ( t h r o w E r r o r $ NotFunction " U n r e c o g n i z e d primitive function args " func ) ($ args ) ( lookup f u n c p r i m i t i v e s ) 100 105 p r i m i t i v e s : : [ ( String .35 90 eval eval eval eval eval eval eval : : L i s p V a l −> ThrowsError L i s p V a l val@ ( String ) = return v a l val@ ( Number ) = return v a l val@ ( Bool ) = return v a l ( L i s t [ Atom " q u o t e " . 110 115 120 unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null parsed then t h r o w E r r o r $ TypeMismatch " n u m b e r " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( L i s t [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " n u m b e r " notNum data L i s p E r r o r = | | | | | | showError showError showError showError showError NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l NotFunction String String UnboundVar String String D e f a u l t String 125 130 135 : : L i s p E r r o r −> String ( UnboundVar message varname ) = message ++ " : " ++ varname ( BadSpecialForm message form ) = message ++ " : " ++ show form ( NotFunction message f u n c ) = message ++ " : " ++ show f u n c ( NumArgs e x p e c t e d f o un d ) = " E x p e c t e d " ++ show e x p e c t e d ++ " a r g s : f o u n d v a l u e s " ++ u n w o r d s L i s t f o u nd s h o w E r r o r ( TypeMismatch e x p e c t e d f o u nd ) = " I n v a l i d t y p e : e x p e c t e d " ++ expected ++ " . numericBinop quot ) . numericBinop mod) . numericBinop ( ∗ ) ) . ( " * " . numericBinop div ) . ( " m o d " ." . ( " / " . numericBinop rem ) ] numericBinop : : ThrowsError numericBinop op numericBinop op f o l d l 1 op ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> LispVal s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 s i n g l e V a l params = mapM unpackNum params > >= return .

/ e r r o r c h e c k " (+ 2) " Expected 2 a r g s : found v a l u e s 2 U n r e c o g n i z e d p r i m i t i v e f u n c t i o n a r g s : " what ?" Some readers have reported that you need to add a --make flag to build this example. / e r r o r c h e c k " (+ 2 ###BOT_TEXT###quot; two ###BOT_TEXT###quot;) " I n v a l i d type : e x p e c t e d number . hs user>> .36 CHAPTER 4. but if it fails on yours. searching out all depedencies listed in the import statements. This tells GHC to build a complete executable. show ) 150 e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l Compile and run the new code. found " two " user>> . The command above works on my system. ERROR CHECKING AND EXCEPTIONS t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . . and try throwing it a couple errors: user>> ghc − package p a r s e c −o e r r o r c h e c k e r r o r c h e c k . give --make a try. and presumably all further listings.

( " string ?" . s t r B o o l B i n o p (<=) ) . conditionals. ( " <" . ( " string =? " . ( " >=" . ( " string <=? " . s t r B o o l B i n o p (==) ) . these both take exactly 2 arguments and return a boolean.Chapter 5 Evaluation. Instead of taking a variable number of arguments and returning an integer. We’ll add boolean operators. They differ from each other only in the type of argument they expect. ( " >" . b o o l B o o l B i n o p ( | | ) ) . numBoolBinop (/=) ) . ( " && " . ( " string >=? " . we’ll flesh out our primitive list so that it does something more than calculate. numBoolBinop (==) ) . numBoolBinop (<=) ) . s t r B o o l B i n o p ( > ) ) . ( " /= " . and so on. numBoolBinop ( > ) ) . s t r B o o l B i n o p (>=) ) ] These depend on helper functions that we haven’t written yet: numBoolBinop and strBoolBinop. ( " || " . Part 2 Additional Primitives: Partial Application Now that we can deal with type errors. numBoolBinop (>=) ) . Start by adding the following into the list of primitives: 109 110 111 112 113 114 115 116 117 118 119 120 • live version • discussion • edit • comment • report an error [edit section] ( "=" . b o o l B o o l B i n o p (&&) ) . ( " <=" . bad arguments. so let’s factor the duplication into a generic boolBinop function that’s parameteried by the unpacker function it applies to its arguments: 126 127 128 129 b ool Bi no p : : ( L i s p V a l −> ThrowsError a ) −> ( a −> a −> Bool ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l b ool Bi no p unpacker op a r g s = i f length a r g s /= 2 then t h r o w E r r o r $ NumArgs 2 a r g s e l s e do l e f t <− unpacker $ a r g s !! 0 37 . numBoolBinop ( < ) ) . and some basic string operations.

EVALUATION. P a r s e c hiding ( s p a c e s ) 5 main : : IO ( ) main = do . take a look at the type signature. E r r o r Text . By parameterizing different parts of the behavior.hs) module import import import import Main where Monad System . you make the function more reusable.1: A simple parser. it will silently convert it to the string representation.38 130 CHAPTER 5. Any function can be turned into an infix operator by wrapping it in backticks ( ‘op‘). This works similarly to unpackNum. now with several primitive operators (operatorparser. 146 147 148 149 150 unpackStr : : L i s p V a l −> ThrowsError String unpackStr ( String s ) = return s unpackStr ( Number s ) = return $ show s unpackStr ( Bool s ) = return $ show s unpackStr n o t S t r i n g = t h r o w E r r o r $ TypeMismatch " string " notString And we use similar code to unpack booleans: 152 153 154 unpackBool : : L i s p V a l −> ThrowsError Bool unpackBool ( Bool b ) = return b unpackBool notBool = t h r o w E r r o r $ TypeMismatch " boolean " notBool The complete code is therefore: Listing 5. PART 2 r i g h t <− unpacker $ a r g s !! 1 return $ Bool $ l e f t ‘ op ‘ right 131 Because each arg may throw a type mismatch. and the second is the actual operation to perform. we have to unpack them sequentially. We then apply the operation to the two arguments and wrap the result in the Bool constructor. Environment C o n t r o l . Again. Monad . boolBinop takes two functions as its first two arguments: the first is used to unpack the arguments from LispVals to native Haskell types. Also. in a do-block (for the Error monad). pattern matching against the value and either returning it or throwing an error. Now we define three functions that specialize boolBinop with different unpackers: 133 134 135 numBoolBinop = b oo lB ino p unpackNum s t r B o o l B i n o p = b oo lB ino p unpackStr b o o l B o o l B i n o p = boo lBi no p unpackBool We haven’t told Haskell how to unpack strings from LispVals yet. P a r s e r C o m b i n a t o r s . if passed a primitive value that could be interpreted as a string (such as a number or boolean).

ADDITIONAL PRIMITIVES: PARTIAL APPLICATION a r g s <− getArgs e v a l e d <− return $ liftM show $ readExpr ( a r g s putStrLn $ e x t r a c t V a l u e $ t r a p E r r o r e v a l e d symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + ./ : < = ? > @ ^ _ ~ # " 15 39 10 !! 0) > >= e v a l 20 readExpr : : String −> ThrowsError L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e 25 30 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 35 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . read ) $ many1 d i g i t 40 45 50 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal : : L i s p V a l −> String showVal ( String c o n t e n t s ) = " \ " " ++ c o n t e n t s ++ " \ " " showVal ( Atom name ) = name 55 60 65 70 75 . ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " .

numericBinop ( ∗ ) ) . ( " || " . EVALUATION. ( " * " . ( " r e m a i n d e r " . s t r B o o l B i n o p (==) ) . ( " > " . ( " / " . s t r B o o l B i n o p (>=) ) ] numericBinop : : ThrowsError numericBinop op numericBinop op f o l d l 1 op ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> LispVal s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 s i n g l e V a l params = mapM unpackNum params > >= return . b o o l B o o l B i n o p (&&) ) . numericBinop rem ) . numBoolBinop ( < ) ) .40 CHAPTER 5. numBoolBinop (==) ) . ( " >= " . ( " s t r i n g > = ? " . s t r B o o l B i n o p (<=) ) ." . ( " s t r i n g ? " . s t r B o o l B i n o p ( >) ) . " ++ showVal t a i l ++ " ) " u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . [ L i s p V a l ] −> ThrowsError L i s p V a l ) ] p r i m i t i v e s = [ ( " + " . numericBinop quot ) . ( " . ( " <= " . ( " < " . numericBinop mod) . ( " q u o t i e n t " . numericBinop ( − ) ) . ( " m o d " . boolBoolBinop ( | | ) ) . v a l ] ) = return v a l ( L i s t ( Atom f u n c : a r g s ) ) = mapM e v a l a r g s > >= a p p l y f u n c badForm = t h r o w E r r o r $ BadSpecialForm " U n r e c o g n i z e d s p e c i a l f o r m " badForm 90 95 a p p l y : : String −> [ L i s p V a l ] −> ThrowsError L i s p V a l a p p l y f u n c a r g s = maybe ( t h r o w E r r o r $ NotFunction " U n r e c o g n i z e d primitive function args " func ) ($ args ) ( lookup f u n c p r i m i t i v e s ) 100 105 110 115 120 p r i m i t i v e s : : [ ( String . numBoolBinop (>=) ) . PART 2 80 showVal ( Number c o n t e n t s ) = show c o n t e n t s showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ u n w o r d s L i s t c o n t e n t s ++ " ) " showVal ( D o t t e d L i s t head t a i l ) = " ( " ++ u n w o r d s L i s t head ++ " . ( " s t r i n g < = ? " . ( " & & " . numBoolBinop ( > ) ) . 125 130 b o o l B i n o p : : ( L i s p V a l −> ThrowsError a ) −> ( a −> a −> Bool ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l b o o l B i n o p u n p a c k e r op a r g s = i f length a r g s /= 2 then t h r o w E r r o r $ NumArgs 2 a r g s e l s e do l e f t <− u n p a c k e r $ a r g s ! ! 0 r i g h t <− u n p a c k e r $ a r g s ! ! 1 return $ Bool $ l e f t ‘ op ‘ r i g h t numBoolBinop = b o o l B i n o p unpackNum strBoolBinop = boolBinop unpackStr b o o l B o o l B i n o p = b o o l B i n o p unpackBool unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n 135 . ( " s t r i n g = ? " . numBoolBinop (<=) ) . numBoolBinop (/=) ) . map showVal 85 instance Show L i s p V a l where show = showVal eval eval eval eval eval eval eval : : L i s p V a l −> ThrowsError L i s p V a l val@ ( String ) = return v a l val@ ( Number ) = return v a l val@ ( Bool ) = return v a l ( L i s t [ Atom " q u o t e " . numericBinop (+) ) . numericBinop div ) . ( " = " . ( " / = " . Number .

ADDITIONAL PRIMITIVES: PARTIAL APPLICATION unpackNum ( String n ) = l e t p a r s e d = reads n in i f null parsed then t h r o w E r r o r $ TypeMismatch " n u m b e r " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( L i s t [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " n u m b e r " notNum unpackStr unpackStr unpackStr unpackStr unpackStr : : L i s p V a l −> ThrowsError String ( String s ) = return s ( Number s ) = return $ show s ( Bool s ) = return $ show s n o t S t r i n g = t h r o w E r r o r $ TypeMismatch " s t r i n g " n o t S t r i n g 41 140 145 150 unpackBool : : L i s p V a l −> ThrowsError Bool unpackBool ( Bool b ) = return b unpackBool n o t B o o l = t h r o w E r r o r $ TypeMismatch " b o o l e a n " n o t B o o l 155 160 data L i s p E r r o r = | | | | | | showError showError showError showError showError NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l NotFunction String String UnboundVar String String D e f a u l t String 165 170 : : L i s p E r r o r −> String ( UnboundVar message varname ) = message ++ " : " ++ varname ( BadSpecialForm message form ) = message ++ " : " ++ show form ( NotFunction message f u n c ) = message ++ " : " ++ show f u n c ( NumArgs e x p e c t e d f o un d ) = " E x p e c t e d " ++ show e x p e c t e d ++ " a r g s : f o u n d v a l u e s " ++ u n w o r d s L i s t f o u nd s h o w E r r o r ( TypeMismatch e x p e c t e d f o u nd ) = " I n v a l i d t y p e : e x p e c t e d " ++ expected ++ " . / s i m p l e p a r s e r "( string =? ###BOT_TEXT###quot; test ###BOT_TEXT###quot; ###BOT_TEXT###quot; test ###BOT_TEXT###quot;) " . / s i m p l e p a r s e r "( string <? ###BOT_TEXT###quot; abc ###BOT_TEXT###quot; ###BOT_TEXT###quot; bba ###BOT_TEXT###quot;) " . show ) 185 e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l Let’s compile and test this to make sure it’s working. hs . / s i m p l e p a r s e r "(> 2 3) " . before we proceed to the next feature: user>> user>> #t user>> #f user>> #t user>> #t user>> ghc − package p a r s e c −o s i m p l e p a r s e r o p e r a t o r p a r s e r . f o u n d " ++ show f o u nd s h o w E r r o r ( P a r s e r p a r s e E r r ) = " P a r s e e r r o r a t " ++ show p a r s e E r r instance Show L i s p E r r o r where show = s h o w E r r o r 175 instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " A n e r r o r h a s o c c u r r e d " st r M sg = D e f a u l t 180 type ThrowsError = Either L i s p E r r o r t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . / s i m p l e p a r s e r "(< 2 3) " . / s i m p l e p a r s e r " ( >= 3 3) " .

5 1) ) ###BOT_TEXT###quot; unequal ###BOT_TEXT###quot;) " 9 List Primitives: car. (car (a)) = a 3. evaluate the alternative. / s i m p l e p a r s e r "( if (= 3 3) (+ 2 3 (. hs user>> . (car (a b c)) = a 2. / s i m p l e p a r s e r "( if (> 2 3) ###BOT_TEXT###quot; no ###BOT_TEXT###quot; ###BOT_TEXT###quot; yes ###BOT_TEXT###quot;) " " yes " user>> . (car a) = error (not a list) 5. Compile and run this. and you’ll be able to play around with conditionals: user>> ghc − package p a r s e c −o s i m p l e p a r s e r c o n d i t i o n a l p a r s e r . As with standard Scheme. our evaluator considers #f to be false and any other value to be true: 93 94 95 96 97 e v a l ( Li s t [ Atom " if " . we evaluate the consequent. c)) = a 4. PART 2 Conditionals: Pattern Matching 2 [edit section] Now. conseq . cdr. The others can be any Scheme forms. Otherwise. EVALUATION. and cons [edit section] For good measure. Here. (car (a b . (car a b) = error (car takes only one argument) We can translate these fairly straightforwardly into pattern clauses. these are somewhat more complicated than their definitions in many Lisps. It’s easiest to think of them in terms of their effect on printed S-expressions: 1. we’re looking for a 4-element list. and if it’s false. evaluate. we’ll proceed to adding an if-clause to our evaluator. recalling that (x : xs) divides a list into the first element and the rest: . We take the first element. a l t ] ) = do r e s u l t <− e v a l pred case r e s u l t of Bool False −> e v a l a l t otherwise −> e v a l c o n s e q This is another example of nested pattern-matching. The first element must be the atom if.42 #t CHAPTER 5. Because we’ve chosen to represent our lists as Haskell algebraic data types instead of pairs. pred . lets also add in the basic list-handling primitives.

enough that we should go through each clause caseby-case. (cdr a) = error (not list) 7. it’s like tacking that anything onto the front of the list: 182 c o n s [ x . If you cons together anything with Nil. Li st xs ] = return $ L ist $ [ x ] ++ xs However. AND CONS 167 168 169 170 171 43 car car car car car : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ Li st ( x : xs ) ] = return x [ D o t t e d L i s t ( x : xs ) ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " pair " badArg badArgLi s t = t h r o w E r r o r $ NumArgs 1 badArgList Let’s do the same with cdr: 1. c) 5. The other ones translate to separate clauses: 173 174 175 176 177 178 We can represent the first 3 cases with a single clause. taking into account the improper tail: 183 c o n s [ x . (cdr (a b . (cdr (a . CDR. you get a DottedList. b)) = b 6. This is because such a cons cell isn’t terminated by the normal Nil that most lists are. (cdr a b) = error (too many args) ’() as List []. c)) = (b . (cdr (a)) = NIL 4. Our parser represents cdr cdr cdr cdr cdr cdr : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ Li st ( x : xs ) ] = return $ L ist xs [ DottedList ( : xs ) x ] = return $ D o t t e d L i s t xs x [ D o t t e d L i s t [ xs ] x ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " pair " badArg badArgLi s t = t h r o w E r r o r $ NumArgs 1 badArgList Cons is a little tricky. the Nil serving as a terminator: 180 181 c o n s : : [ L i s p V a l ] −> ThrowsError L i s p V a l c o n s [ x1 . you end up with a one-item list. if the list is a DottedList. Li st [ ] ] = return $ L ist [ x1 ] If you cons together anything and a list. (cdr (a b)) = (b) 3. D o t t e d L i s t xs x l a s t ] = return $ D o t t e d L i s t ( [ x ] ++ xs ) xlast If you cons together two non-lists. or put a list in front. (cdr (a b c)) = (b c) 2.LIST PRIMITIVES: CAR. xs is bound to [] . and when you pattern-match (x : xs) against [x]. . then it should stay a DottedList.

after checking to make sure the lists are the same length. The complete code is therefore: Listing 5. zips the two lists of pairs. P a r s e r C o m b i n a t o r s . ( Bool a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 eqv [ ( Number a r g 1 ) . and are fairly slow. For our purposes. ( String a r g 2 ) ] = return $ Bool $ a r g 1 == arg2 eqv [ ( Atom a r g 1 ) . EVALUATION. the exception being the one for two Lists. x2 ] = return $ D o t t e d L i s t [ x1 ] x2 Finally. ( L ist a r g 2 ) ] = return $ Bool $ ( length a r g 1 == length a r g 2 ) && ( and $ map eqvPair $ zip arg1 arg2 ) 195 196 197 198 199 where e q v P a i r ( x1 .44 184 CHAPTER 5.2: A parser able to handle lists (listparser. x2 ) = case eqv [ x1 . PART 2 c o n s [ x1 . runs eqvPair on them to test if each corresponding pair is equal. P a r s e c hiding ( s p a c e s ) 5 . but is available only within that particular clause of eqv. This. ] = return $ Bool False eqv badArgLi s t = t h r o w E r r o r $ NumArgs 2 badArgList Most of these clauses are self-explanatory. x2 ] of Left e r r −> False Right ( Bool v a l ) −> v a l eqv [ . eqv?. Scheme offers 3 levels of equivalence predicates: eq?. Monad . attempting to cons together more or less than 2 arguments is an error: 185 c o n s badArgLi s t = t h r o w E r r o r $ NumArgs 2 badArgList Our last step is to implement eqv?. and equal?. So we can write one function for both of them and register it under eq? and eqv?. (Atom a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 eqv [ ( D o t t e d L i s t xs x ) . eq? and eqv? are basically the same: they recognize two items as the same if they print the same. ( Number a r g 2 ) ] = return $ Bool $ a r g 1 == arg2 eqv [ ( String a r g 1 ) . Li st $ ys ++ [ y ] ] eqv [ ( Li s t a r g 1 ) . eqvPair is an example of a local definition: it is defined using the where keyword. just like a normal function. and then uses the function and to return false if any of the resulting values is false. ( D o t t e d L i s t ys y ) ] = eqv [ L ist $ xs ++ [ x ] . 187 188 189 190 191 192 193 194 eqv : : [ L i s p V a l ] −> ThrowsError L i s p V a l eqv [ ( Bool a r g 1 ) . E r r o r Text . Environment C o n t r o l .hs) module import import import import Main where Monad System .

/ : < = ? > @ ^ _ ~ # " 15 45 10 !! 0) > >= e v a l 20 readExpr : : String −> ThrowsError L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e 25 30 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 35 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal : : L i s p V a l −> String 55 60 65 70 . CDR. ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . AND CONS main : : IO ( ) main = do a r g s <− getArgs e v a l e d <− return $ liftM show $ readExpr ( a r g s putStrLn $ e x t r a c t V a l u e $ t r a p E r r o r e v a l e d symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + .LIST PRIMITIVES: CAR. read ) $ many1 d i g i t 40 45 50 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ .

car ) ." . numBoolBinop ( > ) ) . ( " || " . s t r B o o l B i n o p (==) ) . eqv ) . ( " >= " . ( " * " .46 75 CHAPTER 5. ( " <= " . numBoolBinop (==) ) . cdr ) . ( " s t r i n g ? " . numericBinop mod) . Number . ( " q u o t i e n t " . v a l ] ) = return v a l ( L i s t [ Atom " i f " . ( " . ( " e q v ? " . ( " s t r i n g < = ? " . numericBinop quot ) . numBoolBinop (/=) ) . cons ) . c o n s e q . ( " > " . PART 2 ++ c o n t e n t s ++ " \ " " contents u n w o r d s L i s t c o n t e n t s ++ " ) " " ( " ++ u n w o r d s L i s t head ++ " . ( " e q ? " . numBoolBinop (<=) ) . boolBoolBinop ( | | ) ) . s t r B o o l B i n o p (>=) ) . ( " cdr " . pred . ( " = " . ( " car " . numericBinop div ) . numericBinop ( − ) ) . a l t ] ) = do r e s u l t <− e v a l pred case r e s u l t o f Bool False −> e v a l a l t otherwise −> e v a l c o n s e q e v a l ( L i s t ( Atom f u n c : a r g s ) ) = mapM e v a l a r g s > >= a p p l y f u n c e v a l badForm = t h r o w E r r o r $ BadSpecialForm " U n r e c o g n i z e d s p e c i a l f o r m " badForm eval eval eval eval eval eval a p p l y : : String −> [ L i s p V a l ] −> ThrowsError L i s p V a l a p p l y f u n c a r g s = maybe ( t h r o w E r r o r $ NotFunction " U n r e c o g n i z e d primitive function args " func ) ($ args ) ( lookup f u n c p r i m i t i v e s ) 105 90 95 100 110 115 120 125 130 p r i m i t i v e s : : [ ( String . numericBinop (+) ) . 135 . ( " s t r i n g = ? " . eqv ) ] numericBinop : : ThrowsError numericBinop op numericBinop op f o l d l 1 op ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> LispVal s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 s i n g l e V a l params = mapM unpackNum params > >= return . ( " m o d " . numericBinop ( ∗ ) ) . b o o l B o o l B i n o p (&&) ) . numBoolBinop ( < ) ) . s t r B o o l B i n o p ( >) ) . map showVal 85 instance Show L i s p V a l where show = showVal : : L i s p V a l −> ThrowsError L i s p V a l val@ ( String ) = return v a l val@ ( Number ) = return v a l val@ ( Bool ) = return v a l ( L i s t [ Atom " q u o t e " . " ++ 80 showVal ( String c o n t e n t s ) = " \ " " showVal ( Atom name ) = name showVal ( Number c o n t e n t s ) = show showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ showVal ( D o t t e d L i s t head t a i l ) = showVal t a i l ++ " ) " u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . ( " / = " . ( " s t r i n g > = ? " . ( " cons " . ( " / " . numBoolBinop (>=) ) . ( " & & " . s t r B o o l B i n o p (<=) ) . numericBinop rem ) . EVALUATION. ( " r e m a i n d e r " . ( " < " . [ L i s p V a l ] −> ThrowsError L i s p V a l ) ] p r i m i t i v e s = [ ( " + " .

D o t t e d L i s t x s x l a s t ] = return $ D o t t e d L i s t ( [ x ] ++ x s ) x l a s t [ x1 . ( L i s t a r g 2 ) ] = return $ Bool $ ( length a r g 1 == length a r g 2 ) && ( and $ map e q v P a i r $ zip a r g 1 a r g 2 ) where e q v P a i r ( x1 . ( Atom a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( D o t t e d L i s t x s x ) . ( String a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Atom a r g 1 ) . L i s t $ y s ++ [ y ] ] eqv [ ( L i s t a r g 1 ) . ( D o t t e d L i s t y s y ) ] = eqv [ L i s t $ x s ++ [ x ] . x2 ] = return $ D o t t e d L i s t [ x1 ] x2 b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t 170 175 180 185 190 195 : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ ( Bool a r g 1 ) . x2 ] o f Left e r r −> False Right ( Bool v a l ) −> v a l eqv [ .LIST PRIMITIVES: CAR. L i s t [ ] ] = return $ L i s t [ x1 ] [ x . L i s t x s ] = return $ L i s t $ [ x ] ++ x s [ x . ( Bool a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Number a r g 1 ) . ] = return $ Bool False eqv b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t . CDR. AND CONS 47 140 b o o l B i n o p : : ( L i s p V a l −> ThrowsError a ) −> ( a −> a −> Bool ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l b o o l B i n o p u n p a c k e r op a r g s = i f length a r g s /= 2 then t h r o w E r r o r $ NumArgs 2 a r g s e l s e do l e f t <− u n p a c k e r $ a r g s ! ! 0 r i g h t <− u n p a c k e r $ a r g s ! ! 1 return $ Bool $ l e f t ‘ op ‘ r i g h t numBoolBinop = b o o l B i n o p unpackNum strBoolBinop = boolBinop unpackStr b o o l B o o l B i n o p = b o o l B i n o p unpackBool unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null parsed then t h r o w E r r o r $ TypeMismatch " n u m b e r " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( L i s t [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " n u m b e r " notNum unpackStr unpackStr unpackStr unpackStr unpackStr : : L i s p V a l −> ThrowsError String ( String s ) = return s ( Number s ) = return $ show s ( Bool s ) = return $ show s n o t S t r i n g = t h r o w E r r o r $ TypeMismatch " s t r i n g " n o t S t r i n g 145 150 155 160 165 unpackBool : : L i s p V a l −> ThrowsError Bool unpackBool ( Bool b ) = return b unpackBool n o t B o o l = t h r o w E r r o r $ TypeMismatch " b o o l e a n " n o t B o o l car car car car car cdr cdr cdr cdr cdr cdr cons cons cons cons cons cons eqv eqv eqv eqv eqv eqv : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return x [ DottedList ( x : xs ) ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return $ L i s t x s [ DottedList ( : x s ) x ] = return $ D o t t e d L i s t x s x [ D o t t e d L i s t [ x s ] x ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ x1 . ( Number a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( String a r g 1 ) . x2 ) = case eqv [ x1 .

we’d also like to introduce an equal? function that ignores differences in the type tags and only tests if two values can be interpreted the same. For example. EVALUATION. The obvious way to approach this is to store the unpacking functions in a list and use mapM to execute them in turn. we want to try all of our unpack functions. f o u n d " ++ show f o und sh ow E r r o r ( P a r s e r p a r s e E r r ) = " P a r s e e r r o r a t " ++ show p a r s e E r r instance Show L i s p E r r o r where show = s h o w E r r o r 220 instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " A n e r r o r h a s o c c u r r e d " st r M sg = D e f a u l t 225 type ThrowsError = Either L i s p E r r o r t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . because standard Haskell only lets you put objects in a list if they’re the same type. return true. (eqv? 2 "2") = #f.48 200 CHAPTER 5. Basically. so you can’t store them in the same list. show ) 230 e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l Compile and run to try out the new list functions: user>> user>> 2 user>> (3 4) user>> 3 ghc − package p a r s e c −o eqv l i s t p a r s e r . yet we’d like (equal? 2 "2") = #t. . PART 2 205 data L i s p E r r o r = | | | | | | sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l NotFunction String String UnboundVar String String D e f a u l t String 210 215 : : L i s p E r r o r −> String ( UnboundVar message varname ) = message ++ " : " ++ varname ( BadSpecialForm message form ) = message ++ " : " ++ show form ( NotFunction message f u n c ) = message ++ " : " ++ show f u n c ( NumArgs e x p e c t e d f o u nd ) = " E x p e c t e d " ++ show e x p e c t e d ++ " a r g s : f o u n d v a l u e s " ++ u n w o r d s L i s t f o und sh ow E r r o r ( TypeMismatch e x p e c t e d f o u nd ) = " I n v a l i d t y p e : e x p e c t e d " ++ expected ++ " . this doesn’t work. The various unpacker functions return different types. / eqv "( car ( cdr ( cons 2 ’(3 4) )))" Equal? and Weak Typing: Heterogenous Lists [edit section] Since we introduced weak typing above. Unfortunately. hs . / eqv "( cdr ’(2 3 4) )" . and if any of them result in Haskell values that are equal. / eqv "( car ’(2 3 4) )" .

The first thing we need to do is define a data type that can hold any function from a LispVal −> something. provided that that “something” supports equality: 201 data Unpacker = f o r a l l a .” We’ll have to wrap our functions with the AnyUnpacker constructor. This . and they’re often compatible between implementations (existential types work in both Hugs and GHC and are a candidate for standardization). we enter a do-block for the ThrowsError monad. If there is an error anywhere within the two unpackers. it returns false. Extensions are fairly common in the Haskell world: they’re basically necessary to create any reasonably large program. This retrieves the Haskell values of the two LispVals. and then tests whether they’re equal. a r g 2 ] return $ Bool $ ( p r i m i t i v e E q u a l s | | l e t ( Bool x ) = eqvEqual s in x ) e q u a l badArgLi s t = t h r o w E r r o r $ NumArgs 2 badArgList The first action makes a heterogenous list of [unpackNum. unpackBool ]. and may throw an error. using the const function because catchError expects a function to apply to the error value. except for the type constraint. Eq a = > AnyUnpacker ( L i s p V a l −> ThrowsError a ) This is like any normal algebraic datatype. we can define equal? in terms of these helpers: 210 211 212 213 214 215 216 e q u a l : : [ L i s p V a l ] −> ThrowsError L i s p V a l e q u a l [ arg1 . Finally. AnyUnpacker unpackBool ] eqvEqual s <− eqv [ arg1 . Rather than jump straight to the equal? function. It says. “For any type that is an instance of Eq. you can define an Unpacker that takes a function from LispVal to that type. and then maps the partially-applied (unpackEquals arg1 arg2) over it. let’s first define a helper function that takes an Unpacker and then determines if two LispVals are equal when it unpacks them: 203 204 205 206 207 208 unpackEquals : : L i s p V a l −> L i s p V a l −> Unpacker −> ThrowsError Bool unpackEquals a r g 1 a r g 2 ( AnyUnpacker unpacker ) = do unpacked1 <− unpacker a r g 1 unpacked2 <− unpacker a r g 2 return $ unpacked1 == unpacked2 ‘ c a t c h E r r o r ‘ ( const $ return False ) After pattern-matching to retrieve the actual function. subject to typeclass constraints. but then we can create a list of Unpackers that does just what we want it. unpackStr. AnyUnpacker unpackStr .EQUAL? AND WEAK TYPING: HETEROGENOUS LISTS 49 We’ll get around this by using a GHC extension—Existential Types—that lets us create a heterogenous list. a r g 2 ] = do p r i m i t i v e E q u a l s <− liftM or $ mapM ( unpackEquals a r g 1 a r g 2 ) [ AnyUnpacker unpackNum .

Since we want equal? to be looser than eqv?. c a r ) . Monad . ( " cdr " . To use these functions. E r r o r Text .50 CHAPTER 5. eqv ) . cons ) . eqv ) . ( " eqv ?" . so we use the Prelude function or to return true if any single one of them is true. ( " eq ?" . ( " cons " . c a r ) . ( " equal ?" . insert them into our primitives list: 126 127 128 129 130 131 ( " car " . P a r s e c hiding ( s p a c e s ) 5 10 main : : IO ( ) main = do a r g s <− getArgs e v a l e d <− return $ liftM show $ readExpr ( a r g s putStrLn $ e x t r a c t V a l u e $ t r a p E r r o r e v a l e d symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . EVALUATION. ( " cons " . it should return true whenever eqv? does so. returning a LispVal. The second action tests the two arguments with eqv?. and then returns x. The result of a letexpression is the expression following the keyword in./ : < = ? > @ ^ _ ~ # " !! 0) > >= e v a l 15 20 readExpr : : String −> ThrowsError L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e 25 data L i s p V a l = | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String . PART 2 gives a list of Bools. ( " equal ?" . eqv ) . ( " eqv ?" . equal ) 1 2 3 4 5 6 Listing 5. c o n s ) . equal? ors both of these values together and wraps the result in the Bool constructor. ( " cdr " . ( " eq ?" . The let (Bool x)= eqvEquals in x is a quick way of extracting a value from an algebraic type: it pattern matches Bool x against the eqvEquals value. see exercise #2 in this section). Finally. This also lets us avoid handling cases like the list or dotted-list (though this introduces a bug. Environment C o n t r o l . c d r ) . P a r s e r C o m b i n a t o r s . e q u a l ) ] ( " car " . c d r ) . eqv ) .hs) module import import import import Main where Monad System .3: A parser able to compare values of different types (equalparser.

map showVal 85 instance Show L i s p V a l where show = showVal eval eval eval eval eval eval : : L i s p V a l −> ThrowsError L i s p V a l val@ ( String ) = return v a l val@ ( Number ) = return v a l val@ ( Bool ) = return v a l ( L i s t [ Atom " q u o t e " . a l t ] ) = do r e s u l t <− e v a l pred case r e s u l t o f 90 95 . " ++ 55 60 65 70 75 80 u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . pred .EQUAL? AND WEAK TYPING: HETEROGENOUS LISTS | Bool Bool p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal : : L i s p V a l −> String showVal ( String c o n t e n t s ) = " \ " " showVal ( Atom name ) = name showVal ( Number c o n t e n t s ) = show showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ showVal ( D o t t e d L i s t head t a i l ) = showVal t a i l ++ " ) " ++ c o n t e n t s ++ " \ " " contents u n w o r d s L i s t c o n t e n t s ++ " ) " " ( " ++ u n w o r d s L i s t head ++ " . ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . read ) $ many1 d i g i t 50 51 30 35 40 45 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ . c o n s e q . v a l ] ) = return v a l ( L i s t [ Atom " i f " .

numericBinop ( − ) ) . s t r B o o l B i n o p (<=) ) . numBoolBinop (<=) ) . s t r B o o l B i n o p (>=) ) . numBoolBinop (>=) ) . ( " s t r i n g > = ? " . ( " car " . numericBinop quot ) . ( " cons " . ( " & & " . ( " equal ?" . ( " s t r i n g ? " .52 CHAPTER 5. numBoolBinop (==) ) . numBoolBinop ( < ) ) . boolBoolBinop ( | | ) ) . ( " q u o t i e n t " . numericBinop mod) . numericBinop ( ∗ ) ) . ( " e q ? " . numericBinop div ) . ( " s t r i n g = ? " . car ) . numericBinop rem ) . numBoolBinop (/=) ) . cdr ) . ( " > " . EVALUATION. ( " m o d " . ( " r e m a i n d e r " . 135 140 b o o l B i n o p : : ( L i s p V a l −> ThrowsError a ) −> ( a −> a −> Bool ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l b o o l B i n o p u n p a c k e r op a r g s = i f length a r g s /= 2 then t h r o w E r r o r $ NumArgs 2 a r g s e l s e do l e f t <− u n p a c k e r $ a r g s ! ! 0 r i g h t <− u n p a c k e r $ a r g s ! ! 1 return $ Bool $ l e f t ‘ op ‘ r i g h t numBoolBinop = b o o l B i n o p unpackNum strBoolBinop = boolBinop unpackStr b o o l B o o l B i n o p = b o o l B i n o p unpackBool unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null parsed then t h r o w E r r o r $ TypeMismatch " n u m b e r " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( L i s t [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " n u m b e r " notNum unpackStr :: L i s p V a l −> ThrowsError String 145 150 155 . ( " = " . ( " <= " . numBoolBinop ( > ) ) . ( " >= " . eqv ) . ( " < " . ( " * " ." . ( " . b o o l B o o l B i n o p (&&) ) . PART 2 Bool False −> e v a l a l t otherwise −> e v a l c o n s e q e v a l ( L i s t ( Atom f u n c : a r g s ) ) = mapM e v a l a r g s > >= a p p l y f u n c e v a l badForm = t h r o w E r r o r $ BadSpecialForm " U n r e c o g n i z e d s p e c i a l f o r m " badForm 100 a p p l y : : String −> [ L i s p V a l ] −> ThrowsError L i s p V a l a p p l y f u n c a r g s = maybe ( t h r o w E r r o r $ NotFunction " U n r e c o g n i z e d primitive function args " func ) ($ args ) ( lookup f u n c p r i m i t i v e s ) 105 110 115 120 125 130 p r i m i t i v e s : : [ ( String . equal ) ] numericBinop : : ThrowsError numericBinop op numericBinop op f o l d l 1 op ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> LispVal s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 s i n g l e V a l params = mapM unpackNum params > >= return . s t r B o o l B i n o p (==) ) . ( " || " . eqv ) . ( " s t r i n g < = ? " . cons ) . ( " e q v ? " . [ L i s p V a l ] −> ThrowsError L i s p V a l ) ] p r i m i t i v e s = [ ( " + " . ( " / " . numericBinop (+) ) . s t r B o o l B i n o p ( >) ) . Number . ( " cdr " . ( " / = " .

EQUAL? AND WEAK TYPING: HETEROGENOUS LISTS unpackStr unpackStr unpackStr unpackStr ( String s ) = return s ( Number s ) = return $ show s ( Bool s ) = return $ show s n o t S t r i n g = t h r o w E r r o r $ TypeMismatch " s t r i n g " n o t S t r i n g 53 160 165 unpackBool : : L i s p V a l −> ThrowsError Bool unpackBool ( Bool b ) = return b unpackBool n o t B o o l = t h r o w E r r o r $ TypeMismatch " b o o l e a n " n o t B o o l car car car car car cdr cdr cdr cdr cdr cdr cons cons cons cons cons cons eqv eqv eqv eqv eqv eqv : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return x [ DottedList ( x : xs ) ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return $ L i s t x s [ DottedList ( : x s ) x ] = return $ D o t t e d L i s t x s x [ D o t t e d L i s t [ x s ] x ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ x1 . ( Atom a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( D o t t e d L i s t x s x ) . ( String a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Atom a r g 1 ) . ( Bool a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Number a r g 1 ) . AnyUnpacker unpackBool ] e q v E q u a l s <− eqv [ arg1 . Eq a = > AnyUnpacker ( L i s p V a l −> ThrowsError a ) unpa ckEqual s : : L i s p V a l −> L i s p V a l −> Unpacker −> ThrowsError Bool unpa ckEqual s a r g 1 a r g 2 ( AnyUnpacker u n p a c k e r ) = do unpacked1 <− u n p a c k e r a r g 1 unpacked2 <− u n p a c k e r a r g 2 return $ unpacked1 == unpacked2 ‘ c a t c h E r r o r ‘ ( const $ return False ) e q u a l : : [ L i s p V a l ] −> ThrowsError L i s p V a l e q u a l [ arg1 . a r g 2 ] = do p r i m i t i v e E q u a l s <− liftM or $ mapM ( unpa ckEqual s a r g 1 a r g 2 ) [ AnyUnpacker unpackNum . x2 ] o f Left e r r −> False Right ( Bool v a l ) −> v a l eqv [ . L i s t $ y s ++ [ y ] ] eqv [ ( L i s t a r g 1 ) . ] = return $ Bool False eqv b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t data Unpacker = f o r a l l a . ( D o t t e d L i s t y s y ) ] = eqv [ L i s t $ x s ++ [ x ] . x2 ) = case eqv [ x1 . x2 ] = return $ D o t t e d L i s t [ x1 ] x2 b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t 170 175 180 185 190 195 : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ ( Bool a r g 1 ) . ( Number a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( String a r g 1 ) . a r g 2 ] return $ Bool $ ( p r i m i t i v e E q u a l s | | l e t ( Bool x ) = e q v E q u a l s in x ) e q u a l b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t data L i s p E r r o r = | | | NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l 200 205 210 215 220 . L i s t x s ] = return $ L i s t $ [ x ] ++ x s [ x . D o t t e d L i s t x s x l a s t ] = return $ D o t t e d L i s t ( [ x ] ++ x s ) x l a s t [ x1 . ( L i s t a r g 2 ) ] = return $ Bool $ ( length a r g 1 == length a r g 2 ) && ( and $ map e q v P a i r $ zip a r g 1 a r g 2 ) where e q v P a i r ( x1 . L i s t [ ] ] = return $ L i s t [ x1 ] [ x . AnyUnpacker unpackStr .

test ) user>> . / s i m p l e p a r s e r "( cdr ’(a simple test ))" ( simple t e s t ) user>> . 2. equal? has a bug in that a list of values is compared using eqv? instead of equal?. while you’d .54 CHAPTER 5. / s i m p l e p a r s e r "( car ( cdr ’(a simple test )))" simple user>> . f o u n d " ++ show f o und sh ow E r r o r ( P a r s e r p a r s e E r r ) = " P a r s e e r r o r a t " ++ show p a r s e E r r sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r instance Show L i s p E r r o r where show = s h o w E r r o r instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " A n e r r o r h a s o c c u r r e d " st r M sg = D e f a u l t type ThrowsError = Either L i s p E r r o r t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . (equal? ’(1 "2") ’(1 2)) = #f. / s i m p l e p a r s e r "( eqv ? 3 3) " #t user>> . EVALUATION. / s i m p l e p a r s e r "( eqv ? 1 3) " #f user>> . PART 2 | NotFunction String String | UnboundVar String String | D e f a u l t String 225 230 : : L i s p E r r o r −> String ( UnboundVar message varname ) = message ++ " : " ++ varname ( BadSpecialForm message form ) = message ++ " : " ++ show form ( NotFunction message f u n c ) = message ++ " : " ++ show f u n c ( NumArgs e x p e c t e d f o u nd ) = " E x p e c t e d " ++ show e x p e c t e d ++ " a r g s : f o u n d v a l u e s " ++ u n w o r d s L i s t f o und sh ow E r r o r ( TypeMismatch e x p e c t e d f o u nd ) = " I n v a l i d t y p e : e x p e c t e d " ++ expected ++ " . change the definition of if so that the predicate accepts only Bool values and throws an error on any others. hs user>> . For example. / s i m p l e p a r s e r "( eqv ? ’ atom ’ atom )" #t Exercises 1. / s i m p l e p a r s e r "( car ’(( this is ) a test ))" ( this is ) user>> . / s i m p l e p a r s e r "( cons ’( this is ) ’() )" (( this is ) ) user>> . you need to enable GHC extensions with -fglasgow-exts: user>> ghc − package p a r s e c − f g l a s g o w − e x t s −o p a r s e r e q u a l p a r s e r . show ) 235 240 245 e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l To compile this code. Instead of treating any non-false value as true. / s i m p l e p a r s e r "( cons ’( this is ) ’ test )" (( this is ) .

You can either do this explicitly. . You don’t yet know enough to do string-set!. or factor the list clause into a separate helper function that is parameterized by the equality testing function. but you’ll have enough information after the next two sections.EXERCISES 55 expect it to be true. Add the rest of the string functions. 4. 3. Implement cond and case expressions. Change equal? so that it continues to ignore types as it recurses into list structures. this is difficult to implement in Haskell. following the example in eqv?.

56 CHAPTER 5. EVALUATION. PART 2 .

we’ve been content to evaluate single expressions from the command line. Instead of executing a whole program at once. we define a couple helper functions to simplify some of our IO tasks.” We’d like to be able to define new functions and variables. We’ll want a function that prints out a string and immediately flushes the stream. This reads in expressions from the console one at a time and executes them interactively. after the next section). we’re going to build a readeval-print loop. First. Later expressions can reference variables set by earlier ones (or will be able to. Next. printing the result and exiting afterwards. Add the following to the top of the program: 6 • live version • discussion • edit • comment • report an error import IO hiding ( try ) We have to hide the try function (used in the IO module for exception handling) because we use Parsec’s try function. otherwise. This is fine for a calculator.Chapter 6 Building a REPL: Basic I/O So far. and refer to them later. we create a function that prints out a prompt and reads in a line of input: 254 255 readPrompt : : String −> IO String readPrompt prompt = f l u s h S t r prompt >> getLine Pull the code to parse and evaluate a string and trap the errors out of main into its own function: 57 . we need to build a system that can execute multiple statements without exiting the program. but isn’t what most people think of as “programming. letting you build up libraries of functions. 251 252 f l u s h S t r : : String −> IO ( ) f l u s h S t r s t r = putStr s t r >> hFlush stdout Then. we need to import some additional IO functions. printing the result after each expression. But before we can do this. output might sit in output buffers and the user will never see prompts or results.

and print the output. We want to read input. and include the type constraint Monad m =>. we can write our REPL easily: 270 271 runRepl : : IO ( ) runRepl = u n t i l (== " quit " ) ( readPrompt " Lisp >>> " ) evalAndPrint And change our main function so it either executes a single expression. So we need to roll our own loop: 263 264 265 266 267 268 until : : Monad m = > ( a −> Bool ) −> m a −> ( a −> m ( ) ) −> m ( ) u n t i l pred prompt a c t i o n = do r e s u l t <− prompt i f pred r e s u l t then return ( ) e l s e a c t i o n r e s u l t >> u n t i l pred prompt a c t i o n The underscore after the name is a typical naming convention in Haskell for monadic functions that repeat but do not return a value. but we wouldn’t be able to break out of it. Each of the latter two is generalized over any monad. Now that we have all the machinery in place. until_ takes a predicate that signals when to stop. all in an infinite loop. and a function-returning-an-action to do to the input. we’d get an infinite loop. BUILDING A REPL e v a l S t r i n g : : String −> IO String e v a l S t r i n g expr = return $ e x t r a c t V a l u e $ t r a p E r r o r ( liftM show $ readExpr expr >>= e v a l ) And write a function that evaluates a string and prints the result: 260 261 e v a l A n d P r i n t : : String −> IO ( ) e v a l A n d P r i n t expr = e v a l S t r i n g expr >>= putStrLn Now it’s time to tie it all together. The built-in function interact almost does what we want. not just IO. Note also that we can write recursive actions just as we write recursive functions.58 257 258 CHAPTER 6. Environment . but doesn’t loop. repeat .hs) module Main where import Monad import System .1: A parser implementing the REPL method (replparser. If we used the combination sequence . or enters the REPL and continues evaluating expressions until we type "quit": 8 9 10 11 12 13 main : : IO ( ) main = do a r g s <− getArgs case length a r g s of 0 −> runRepl 1 −> e v a l A n d P r i n t $ a r g s ! ! 0 otherwise −> putStrLn " Program takes only 0 or 1 argument " The complete code is therefore: Listing 6. interact. That’s why we write their types using the type variable m. perform a function. an action to perform before the test.

/ : < = ? > @ ^ _ ~ # " readExpr : : String −> ThrowsError L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e 25 5 10 15 20 30 data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 35 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number .59 import C o n t r o l . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ 60 65 70 . E r r o r import Text . ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . P a r s e c hiding ( s p a c e s ) import IO hiding ( try ) main : : IO ( ) main = do a r g s <− getArgs case length a r g s o f 0 −> runRepl 1 −> e v a l A n d P r i n t $ a r g s ! ! 0 otherwise −> putStrLn " P r o g r a m t a k e s o n l y 0 o r 1 a r g u m e n t " symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . Monad . read ) $ many1 d i g i t 40 45 50 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s 55 parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ . P a r s e r C o m b i n a t o r s .

" ++ 85 u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . numBoolBinop (<=) ) . eqv ) . eqv ) ." . cons ) . cdr ) . ( " || " . c o n s e q . ( " / = " . ( " > " . ( " s t r i n g ? " . [ L i s p V a l ] −> ThrowsError L i s p V a l ) ] p r i m i t i v e s = [ ( " + " . a l t ] ) = do r e s u l t <− e v a l pred case r e s u l t o f Bool False −> e v a l a l t otherwise −> e v a l c o n s e q e v a l ( L i s t ( Atom f u n c : a r g s ) ) = mapM e v a l a r g s > >= a p p l y f u n c e v a l badForm = t h r o w E r r o r $ BadSpecialForm " U n r e c o g n i z e d s p e c i a l f o r m " badForm eval eval eval eval eval eval a p p l y : : String −> [ L i s p V a l ] −> ThrowsError L i s p V a l a p p l y f u n c a r g s = maybe ( t h r o w E r r o r $ NotFunction " U n r e c o g n i z e d primitive function args " func ) ($ args ) ( lookup f u n c p r i m i t i v e s ) p r i m i t i v e s : : [ ( String . ( " s t r i n g = ? " . numBoolBinop (/=) ) . numBoolBinop (==) ) . s t r B o o l B i n o p (==) ) . map showVal instance Show L i s p V a l where show = showVal 90 95 100 : : L i s p V a l −> ThrowsError L i s p V a l val@ ( String ) = return v a l val@ ( Number ) = return v a l val@ ( Bool ) = return v a l ( L i s t [ Atom " q u o t e " . numBoolBinop (>=) ) . ( " & & " . numericBinop ( − ) ) . car ) . s t r B o o l B i n o p ( >) ) . ( " cons " . ( " equal ?" . ( " q u o t i e n t " . boolBoolBinop ( | | ) ) .60 CHAPTER 6. numericBinop div ) . s t r B o o l B i n o p (>=) ) . equal ) ] numericBinop : : ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l 105 110 115 120 125 130 135 . ( " car " . ( " s t r i n g > = ? " . ( " e q v ? " . BUILDING A REPL x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x 75 80 showVal : : L i s p V a l −> String showVal ( String c o n t e n t s ) = " \ " " showVal ( Atom name ) = name showVal ( Number c o n t e n t s ) = show showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ showVal ( D o t t e d L i s t head t a i l ) = showVal t a i l ++ " ) " ++ c o n t e n t s ++ " \ " " contents u n w o r d s L i s t c o n t e n t s ++ " ) " " ( " ++ u n w o r d s L i s t head ++ " . ( " = " . ( " m o d " . s t r B o o l B i n o p (<=) ) . ( " e q ? " . numericBinop (+) ) . ( " * " . numBoolBinop ( > ) ) . numericBinop ( ∗ ) ) . numericBinop quot ) . numBoolBinop ( < ) ) . ( " . ( " / " . numericBinop mod) . ( " >= " . pred . ( " <= " . ( " r e m a i n d e r " . numericBinop rem ) . v a l ] ) = return v a l ( L i s t [ Atom " i f " . ( " < " . b o o l B o o l B i n o p (&&) ) . ( " cdr " . ( " s t r i n g < = ? " .

L i s t $ y s ++ [ y ] ] eqv [ ( L i s t a r g 1 ) . Number . ( Number a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( String a r g 1 ) . x2 ) = case eqv [ x1 . x2 ] = return $ D o t t e d L i s t [ x1 ] x2 b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t 170 175 180 185 190 195 : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ ( Bool a r g 1 ) .61 numericBinop op s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 s i n g l e V a l numericBinop op params = mapM unpackNum params > >= return . L i s t [ ] ] = return $ L i s t [ x1 ] [ x . f o l d l 1 op b o o l B i n o p : : ( L i s p V a l −> ThrowsError a ) −> ( a −> a −> Bool ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l b o o l B i n o p u n p a c k e r op a r g s = i f length a r g s /= 2 then t h r o w E r r o r $ NumArgs 2 a r g s e l s e do l e f t <− u n p a c k e r $ a r g s ! ! 0 r i g h t <− u n p a c k e r $ a r g s ! ! 1 return $ Bool $ l e f t ‘ op ‘ r i g h t numBoolBinop = b o o l B i n o p unpackNum strBoolBinop = boolBinop unpackStr b o o l B o o l B i n o p = b o o l B i n o p unpackBool 150 140 145 155 unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null parsed then t h r o w E r r o r $ TypeMismatch " n u m b e r " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( L i s t [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " n u m b e r " notNum unpackStr unpackStr unpackStr unpackStr unpackStr : : L i s p V a l −> ThrowsError String ( String s ) = return s ( Number s ) = return $ show s ( Bool s ) = return $ show s n o t S t r i n g = t h r o w E r r o r $ TypeMismatch " s t r i n g " n o t S t r i n g 160 165 unpackBool : : L i s p V a l −> ThrowsError Bool unpackBool ( Bool b ) = return b unpackBool n o t B o o l = t h r o w E r r o r $ TypeMismatch " b o o l e a n " n o t B o o l car car car car car cdr cdr cdr cdr cdr cdr cons cons cons cons cons cons eqv eqv eqv eqv eqv eqv : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return x [ DottedList ( x : xs ) ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return $ L i s t x s [ DottedList ( : x s ) x ] = return $ D o t t e d L i s t x s x [ D o t t e d L i s t [ x s ] x ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ x1 . L i s t x s ] = return $ L i s t $ [ x ] ++ x s [ x . x2 ] o f . ( L i s t a r g 2 ) ] = return $ Bool $ ( length a r g 1 == length a r g 2 ) && ( and $ map e q v P a i r $ zip a r g 1 a r g 2 ) where e q v P a i r ( x1 . ( String a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Atom a r g 1 ) . ( Atom a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( D o t t e d L i s t x s x ) . ( Bool a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Number a r g 1 ) . D o t t e d L i s t x s x l a s t ] = return $ D o t t e d L i s t ( [ x ] ++ x s ) x l a s t [ x1 . ( D o t t e d L i s t y s y ) ] = eqv [ L i s t $ x s ++ [ x ] .

show ) e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l 250 flushStr flushStr 255 : : String −> IO ( ) s t r = putStr s t r >> hFlush stdout readPrompt : : String −> IO String readPrompt prompt = f l u s h S t r prompt >> getLine e v a l S t r i n g : : String −> IO String e v a l S t r i n g e x p r = return $ e x t r a c t V a l u e $ t r a p E r r o r ( liftM show $ readExpr e x p r > >= e v a l ) 260 e v a l A n d P r i n t : : String −> IO ( ) evalAndPrint expr = e v a l S t r i n g expr > >= putStrLn . AnyUnpacker unpackBool ] e q v E q u a l s <− eqv [ arg1 . a r g 2 ] return $ Bool $ ( p r i m i t i v e E q u a l s | | l e t ( Bool x ) = e q v E q u a l s in x ) e q u a l b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t data L i s p E r r o r = | | | | | | sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l NotFunction String String UnboundVar String String D e f a u l t String 215 220 225 230 235 : : L i s p E r r o r −> String ( UnboundVar message varname ) = message ++ " : " ++ varname ( BadSpecialForm message form ) = message ++ " : " ++ show form ( NotFunction message f u n c ) = message ++ " : " ++ show f u n c ( NumArgs e x p e c t e d f o u nd ) = " E x p e c t e d " ++ show e x p e c t e d ++ " a r g s : f o u n d v a l u e s " ++ u n w o r d s L i s t f o und sh ow E r r o r ( TypeMismatch e x p e c t e d f o u nd ) = " I n v a l i d t y p e : e x p e c t e d " ++ expected ++ " . f o u n d " ++ show f o und sh ow E r r o r ( P a r s e r p a r s e E r r ) = " P a r s e e r r o r a t " ++ show p a r s e E r r instance Show L i s p E r r o r where show = s h o w E r r o r 240 instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " A n e r r o r h a s o c c u r r e d " st r M sg = D e f a u l t type ThrowsError = Either L i s p E r r o r 245 t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . a r g 2 ] = do p r i m i t i v e E q u a l s <− liftM or $ mapM ( unpa ckEqual s a r g 1 a r g 2 ) [ AnyUnpacker unpackNum . ] = return $ Bool False eqv b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t data Unpacker = f o r a l l a . BUILDING A REPL 200 Left e r r −> False Right ( Bool v a l ) −> v a l eqv [ . AnyUnpacker unpackStr .62 CHAPTER 6. Eq a = > AnyUnpacker ( L i s p V a l −> ThrowsError a ) 205 210 unpa ck Equal s : : L i s p V a l −> L i s p V a l −> Unpacker −> ThrowsError Bool unpa ck Equal s a r g 1 a r g 2 ( AnyUnpacker u n p a c k e r ) = do unpacked1 <− u n p a c k e r a r g 1 unpacked2 <− u n p a c k e r a r g 2 return $ unpacked1 == unpacked2 ‘ c a t c h E r r o r ‘ ( const $ return False ) e q u a l : : [ L i s p V a l ] −> ThrowsError L i s p V a l e q u a l [ arg1 .

/ l i s p Lisp>>> (+ 2 3 ) 5 Lisp>>> ( c o n s t h i s ’ ( ) ) U n r e c o g n i z e d s p e c i a l form : t h i s Lisp>>> ( c o n s 2 3 ) (2 . hs user>> . and try it out: user>> ghc − package p a r s e c − f g l a s g o w − e x t s −o l i s p r e a l p a r s e r . 3) Lisp>>> ( c o n s ’ t h i s ’ ( ) ) ( this ) Lisp>>> q u i t user>> .63 265 until : : Monad m = > ( a −> Bool ) −> m a −> ( a −> m ( ) ) −> m ( ) until pred prompt a c t i o n = do r e s u l t <− prompt i f pred r e s u l t then return ( ) e l s e a c t i o n r e s u l t >> u n t i l pred prompt a c t i o n runRepl : : IO ( ) runRepl = u n t i l (== " q u i t " ) ( readPrompt " L i s p > > > " ) e v a l A n d P r i n t 270 Compile and run the program.

64 CHAPTER 6. BUILDING A REPL .

In Scheme. And when we add closures. we use a feature called state threads. but never change them. all involving monads. arbitrarily deep. letting Haskell manage the aggregate state for us. so that its value changes as the program executes. Nevertheless. using functions to get or set variables. This lets us treat mutable variables as we would in any other programming language. However. the state monad doesn’t work well for us. There are two flavors of state threads: the ST monad creates a stateful computation that 65 • live version • discussion • edit • comment • report an error . LispVal)]. these mappings become a stack of nested environments. if a function returns an integer but modifies a list of string pairs. You specify the state type as a parameter to the monad (eg. and access it via the get and put functions. Instead. You’d specify the initial state via the runState myStateAction initialList. usually within a do-block. A variable lets us save the result of an expression and refer to it later. environments might get saved in an arbitrary Function value. This presents a complication for Haskell. storing mappings from variable names to values. Unfortunately. which returns a pair containing the return value and the final state. when we start dealing with function calls. we get to the good stuff: variables. The simplest is probably the State monad. because the type of data we need to store is fairly complex. a variable can also be reset to new values. String)] Integer). and passed around throughout the program.Chapter 7 Adding Variables and Assignment: Mutable State in Haskell Finally. we could get away with [( String. For a simple top-level environment. there are several ways to simulate state in Haskell. In fact. which lets you hide arbitrary state within the monad and pass it around behind the scenes. because the execution model is built upon functions that return values. something we’re not allowed to do. it would have type State [( String. they might be saved in a variable and passed out of the runState monad entirely.

We’ve created a monad that may contain IO actions that throw a LispError. so a variable in an outer scope is visible to all inner scopes). However. ADDING VARIABLES AND ASSIGNMENT can be executed as a unit. We can’t just use the empty list [] because all accesses to IORefs must be sequenced. We’ll be using one of these— ErrorT—which lets us layer error-handling functionality on top of the IO monad.66 CHAPTER 7. we’ll want a helper action to create an empty environment. We have a mix of IOThrows and IOThrowsError functions. Haskell provides a mechanism known as monad transformers that lets you combine the functionality of multiple monads. without the state escaping to the rest of the program. things get a bit more complicated. the return type of the function. but actions of different types cannot be contained within the same do-block. which should be visible on all subsequent statements. because we’ll be simultaneously dealing with two monads. We can start out by importing Data. Haskell already provides a mechanism— lifting—to bring values of the lower type (IO) into the combined monad. so we need to write it ourselves: . there’s no similar support to bring a value of the untransformed upper type into the combined monad. Since IORefs can only be used within the IO monad. We need IORefs for both the list itself and for individual values because there are two ways that the program can mutate the environment. Our first step is create a type synonym for our combined monad: 287 type IOThrowsError = ErrorT L i s p E r r o r IO Like IOThrows. IORef L i s p V a l ) ] 282 This declares an Env as an IORef holding a list that maps Strings to mutable LispVals. we’ll be using IORefs. The IORef module lets you use stateful variables within the IO monad. and so the type of our null environment is IO Env instead of just plain Env: 284 285 null Env : : IO Env null Env = newIORef [ ] From here. IORef type Env = IORef [ ( String . so we can’t just catch all the exceptions and return only normal values to the IO monad.IORef and defining a type for our environments: 5 import Data . and we will eventually have IO functions within the language itself). ErrorT takes one more argument than plain old Either: we have to specify the type of monad that we’re layering our error-handling functionality over. Or it might use define to add a new variable. a change visible to any function that shares that environment (Scheme allows nested scopes. even if they provide essentially the same functionality. It might use set! to change the value of an individual variable. IOThrowsError is really a type constructor: we’ve left off the last argument. Unfortunately. we also need an Error monad to handle things like unbound variables. The parts that need IO functionality and the parts that may throw exceptions are interleaved. Remember. Since our state has to be interleaved with IO anyway (it persists between lines in the REPL.

Next. so throwError and return (members of MonadError and Monad. Finally. the type signature provided here is not fully general: if we’d left it off. and you don’t want that in a lazily-evaluated pure function. We can’t escape from the IO monad. because it . returning an IO action. Then we pass it to lookup to search for the particular variable we’re interested in. extractValue This uses our previously-defined trapError function to take any error values and convert them to their string representations. The result is passed into extractValue and returned as a value in the IO monad. Since we’re just interested in a true/false value. We’ll start with a function to determine if a given variable is already bound in the environment. 293 294 runIOThrows : : IOThrowsError String −> IO String runIOThrows a c t i o n = runErrorT ( t r a p E r r o r a c t i o n ) >>= return . because a function that performs IO has an effect on the outside world. the compiler would have inferred liftThrows :: MonadError m =>Either e a −> m e. However. so we return False if that value was Nothing and True otherwise (we need to use the const function because maybe expects a function to perform on the result and not just a value). necessary for proper handling of define: 296 297 isBound : : Env −> String −> IO Bool isBound envRef var = readIORef envRef >>= return . we’ll want to define a function to retrieve the current value of a variable: 299 300 301 302 303 getVar : : Env −> String −> IOThrowsError L i s p V a l getVar envRef var = do env <− l i f t I O $ readIORef envRef maybe ( t h r o w E r r o r $ UnboundVar " Getting an unbound variable " var ) ( l i f t I O . But you can run the error computation and catch the errors. we don’t need to deal with the actual IORef that lookup returns. this begins by retrieving the actual environment from the IORef. getVar uses the IOThrowsError monad. we use return to lift that value into the IO monad. maybe False ( const True ) . Methods in typeclasses resolve based on the type of the expression. lookup returns a Maybe value.67 289 290 291 l i f t T h r o w s : : ThrowsError a −> IOThrowsError a l i f t T h r o w s ( Left e r r ) = t h r o w E r r o r e r r l i f t T h r o w s ( Right v a l ) = return v a l This destructures the Either type and either re-throws the error type or returns the ordinary value. We’ll also want a helper function to run the whole top-level IOThrowsError action. readIORef ) ( lookup var env ) Like the previous function. Incidentally. respectively) take on their IOThrowsError definitions. then runs the whole computation via runErrorT. lookup var This first extracts the actual environment value from its IORef via readIORef. Now we’re ready to return to environment handling.

ADDING VARIABLES AND ASSIGNMENT also needs to do some error handling. Similarly. however. There’s one more useful environment function: being able to bind a whole bunch of variables at once. then writes a new list back to that variable consisting of the new (key. ( f l i p writeIORef value ) ) ( lookup var env ) return v a l u e Again. We’ll want a function to handle the special behavior of define. we want to change the variable instead of just reading it. Now we create a function to set values: 305 306 307 308 309 310 s e t V a r : : Env −> String −> L i s p V a l −> IOThrowsError L i s p V a l s e t V a r envRef var v a l u e = do env <− l i f t I O $ readIORef envRef maybe ( t h r o w E r r o r $ UnboundVar " Setting an unbound variable " var ) ( l i f t I O . we return the value we just set. which sets a variable if already bound or creates a new one if not. and then pass it the value. because throwError is a defined for the MonadError typeclass. reads the current value of the environment. we can use it in the former case: 312 313 314 315 316 317 318 319 320 321 d e f i n e V a r : : Env −> String −> L i s p V a l −> IOThrowsError L i s p V a l d e f i n e V a r envRef var v a l u e = do a l r e a d y D e f i n e d <− l i f t I O $ isBound envRef var i f alreadyDefined then s e t V a r envRef var v a l u e >> return v a l u e e l s e l i f t I O $ do v a l u e R e f <− newIORef v a l u e env <− readIORef envRef w r i t e I O R e f envRef ( ( var . Then we lift that whole do-block into the IOThrowsError monad with liftIO. So we use the built-in function flip to switch the arguments of writeIORef around. we use liftIO . where the variable is unbound. The writeIORef action provides a means for this. we first read the environment out of its IORef and run a lookup on it. but takes its arguments in the wrong order (ref −> value instead of value −> ref). variable) pair added to the front of the list. This time. We create an IO action (via do-notation) that creates a new IORef to hold the new variable. v a l u e R e f ) : env ) return v a l u e It’s the latter case that’s interesting. we need to use the liftIO function to lift the readIORef action into the combined monad. though we won’t be using it until the next section: . We might as well build that functionality now. for convenience. Since we’ve already defined a function to set values. however. As a result.68 CHAPTER 7. like what would happen at function invocation. readIORef to generate an IOThrowsError action that reads the returned IORef. We don’t need to use liftIO to throw an error. when we return the value. Finally. of which ErrorT is an instance.

value) pair. and then returns the (name. since it uses a monadic pipeline (rather than do-notation) and a pair of helper functions to do the work. conseq . and then appends the current environment to the end of that (++ env). Atom var . we need to change a few of our IO functions to take an environment. v a l ] ) = return v a l env ( Li st [ Atom " if " . Finally. IORef LispVal) pairs. Now that we have all our environment functions. we need to start using them in the evaluator. It’s best to start with the helper functions. the whole function wires these functions up in a pipeline. starting by reading the existing environment out of its IORef. pred . form ] ) = e v a l env form >>= s e t V a r env var e v a l env ( Li st [ Atom " define " . While we’re at it. extendEnv calls addBinding on each member of bindings (mapM) to create a list of (String. Since Haskell has no global variables. addBinding takes a variable name and value. v a l u e ) = do r e f <− newIORef v a l u e return ( var . apply func e v a l env badForm = t h r o w E r r o r $ BadSpecialForm " Unrecognized special form " badForm eval eval eval eval eval eval eval Since a single environment gets threaded through a whole interactive session. Atom var . r e f ) This is perhaps more complicated than the other functions. we might as well add the set! and define special forms. then passing the result to extendEnv. we’ll have to thread the environment through the evaluator as a parameter.69 323 324 325 326 327 bindVars : : Env −> [ ( String . creates an IORef to hold the new variable . L i s p V a l ) ] −> IO Env bindVars envRef b i n d i n g s = readIORef envRef >>= extendEnv b i n d i n g s >>= newIORef where extendEnv b i n d i n g s env = liftM (++ env ) (mapM addBinding b i n d i n g s ) addBinding ( var . form ] ) = e v a l env form >>= d e f i n e V a r env var e v a l env ( Li st (Atom f u n c : a r g s ) ) = mapM ( e v a l env ) a r g s >>= l i f t T h r o w s . 263 264 265 266 e v a l A n d P r i n t : : Env −> String −> IO ( ) e v a l A n d P r i n t env expr = e v a l S t r i n g env expr >>= putStrLn e v a l S t r i n g : : Env −> String −> IO String . a l t ] ) = do r e s u l t <− e v a l env pred case r e s u l t of Bool False −> e v a l env a l t otherwise −> e v a l env c o n s e q e v a l env ( Li st [ Atom " set !" . then returning a new IORef with the extended environment. 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 : : Env −> L i s p V a l −> IOThrowsError L i s p V a l env val@ ( String ) = return v a l env val@ ( Number ) = return v a l env val@ ( Bool ) = return v a l env (Atom id ) = getVar env id env ( Li st [ Atom " quote " .

Monad . since it’s now somewhat more involved than just running eval AndPrint. it ought to apply it first to whatever’s coming down the monadic pipeline. we initialize the environment with a null variable before starting the program: 276 277 278 279 280 runOne : : String −> IO ( ) runOne expr = nullE nv >>= f l i p e v a l A n d P r i n t expr runRepl : : IO ( ) runRepl = nullEnv >>= u n t i l " ) . The changes to runRepl are a bit more subtle: notice how we added a function composition operator before evalAndPrint. ADDING VARIABLES AND ASSIGNMENT e v a l S t r i n g env expr = runIOThrows $ liftM show $ ( l i f t T h r o w s $ readExpr expr ) >>= e v a l env We need the runIOThrows in evalString because the type of the monad has changed from ThrowsError to IOThrowsError. Similarly.1: A parser able to handle variables (variableparser. P a r s e c hiding ( s p a c e s ) IO hiding ( try ) 5 10 15 main : : IO ( ) main = do a r g s <− getArgs case length a r g s o f 0 −> runRepl 1 −> runOne $ a r g s ! ! 0 otherwise −> putStrLn " P r o g r a m t a k e s o n l y 0 o r 1 a r g u m e n t " symbol :: P a r s e r Char .70 267 CHAPTER 7. E r r o r Data . The function composition tells until_ that instead of taking plain old evalAndPrint as an action. just as we want it. fed from nullEnv. Thus. in this case the result of nullEnv. P a r s e r C o m b i n a t o r s . Finally. we need a liftThrows to bring readExpr into the IOThrowsError monad. Next. IORef Text . evalAndPrint (== " quit " ) ( readPrompt " Lisp >>> We’ve created an additional helper function runOne to handle the singleexpression case.hs) module import import import import import import Main where Monad System . That’s because evalAndPrint now takes an additional Env parameter. Environment C o n t r o l . we need to change our main function to call runOne instead of evaluating evalAndPrint directly: 9 10 11 12 13 14 main : : IO ( ) main = do a r g s <− getArgs case length a r g s of 0 −> runRepl 1 −> runOne $ a r g s ! ! 0 otherwise −> putStrLn " Program takes only 0 or 1 argument " The complete code is therefore: Listing 7. the actual function that gets applied to each line of input is (evalAndPrint env).

’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . " ++ 60 65 70 75 80 .71 symbol = oneOf " ! $ % & | * + . read ) $ many1 d i g i t p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s 40 45 50 55 parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ ./ : < = ? > @ ^ _ ~ # " 20 readExpr : : String −> ThrowsError L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e data L i s p V a l = | | | | | Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool 25 30 35 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal showVal showVal showVal showVal showVal showVal showVal : : L i s p V a l −> String ( String c o n t e n t s ) = " \ " " ( Atom name ) = name ( Number c o n t e n t s ) = show ( Bool True ) = " # t " ( Bool False ) = " # f " ( L i s t c o n t e n t s ) = " ( " ++ ( D o t t e d L i s t head t a i l ) = ++ c o n t e n t s ++ " \ " " contents u n w o r d s L i s t c o n t e n t s ++ " ) " " ( " ++ u n w o r d s L i s t head ++ " .

numericBinop ( ∗ ) ) . numBoolBinop ( < ) ) . map showVal instance Show L i s p V a l where show = showVal 90 95 100 105 : : Env −> L i s p V a l −> IOThrowsError L i s p V a l env val@ ( String ) = return v a l env val@ ( Number ) = return v a l env val@ ( Bool ) = return v a l env ( Atom id ) = g e t V a r env id env ( L i s t [ Atom " q u o t e " . ( " cdr " . s t r B o o l B i n o p (==) ) . c o n s e q . ( " / = " . ( " >= " . ( " s t r i n g > = ? " . ( " e q v ? " . ( " q u o t i e n t " . numericBinop quot ) . car ) . eqv ) . ( " . Number . eqv ) . ( " car " . form ] ) = e v a l env form > >= s e t V a r env v a r e v a l env ( L i s t [ Atom " d e f i n e " . numericBinop ( − ) ) . Atom var ." . cdr ) . s t r B o o l B i n o p (>=) ) . numBoolBinop ( > ) ) . ( " m o d " . numericBinop (+) ) . v a l ] ) = return v a l env ( L i s t [ Atom " i f " . ( " = " . numBoolBinop (==) ) . ( " r e m a i n d e r " . pred . ( " & & " . a l t ] ) = do r e s u l t <− e v a l env pred case r e s u l t o f Bool False −> e v a l env a l t otherwise −> e v a l env c o n s e q e v a l env ( L i s t [ Atom " s e t ! " .72 CHAPTER 7. ( " s t r i n g = ? " . ( " * " . numericBinop mod) . ( " < " . equal ) ] numericBinop : : ThrowsError numericBinop op numericBinop op f o l d l 1 op ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> LispVal s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 s i n g l e V a l params = mapM unpackNum params > >= return . ( " s t r i n g ? " . b o o l B o o l B i n o p (&&) ) . [ L i s p V a l ] −> ThrowsError L i s p V a l ) ] p r i m i t i v e s = [ ( " + " . numericBinop div ) . s t r B o o l B i n o p ( >) ) . ( " equal ?" . numericBinop rem ) . ( " e q ? " . apply func e v a l env badForm = t h r o w E r r o r $ BadSpecialForm " U n r e c o g n i z e d s p e c i a l f o r m " badForm eval eval eval eval eval eval eval a p p l y : : String −> [ L i s p V a l ] −> ThrowsError L i s p V a l a p p l y f u n c a r g s = maybe ( t h r o w E r r o r $ NotFunction " U n r e c o g n i z e d primitive function args " func ) ($ args ) ( lookup f u n c p r i m i t i v e s ) p r i m i t i v e s : : [ ( String . boolBoolBinop ( | | ) ) . ( " > " . s t r B o o l B i n o p (<=) ) . Atom var . form ] ) = e v a l env form > >= d e f i n e V a r env v a r e v a l env ( L i s t ( Atom f u n c : a r g s ) ) = mapM ( e v a l env ) a r g s > >= l i f t T h r o w s . ( " <= " . ( " / " . 110 115 120 125 130 135 140 145 b o o l B i n o p : : ( L i s p V a l −> ThrowsError a ) −> ( a −> a −> Bool ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l . ( " cons " . ( " s t r i n g < = ? " . ADDING VARIABLES AND ASSIGNMENT showVal t a i l ++ " ) " 85 u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . numBoolBinop (/=) ) . numBoolBinop (>=) ) . cons ) . ( " || " . numBoolBinop (<=) ) .

( Atom a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( D o t t e d L i s t x s x ) . L i s t $ y s ++ [ y ] ] eqv [ ( L i s t a r g 1 ) . ( L i s t a r g 2 ) ] = return $ Bool $ ( length a r g 1 == length a r g 2 ) && ( and $ map e q v P a i r $ zip a r g 1 a r g 2 ) where e q v P a i r ( x1 . ( Number a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( String a r g 1 ) . ( Bool a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Number a r g 1 ) . ] = return $ Bool False eqv b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t data Unpacker = f o r a l l a . L i s t x s ] = return $ L i s t $ [ x ] ++ x s [ x . ( String a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Atom a r g 1 ) .73 b o o l B i n o p u n p a c k e r op a r g s = i f length a r g s /= 2 then t h r o w E r r o r $ NumArgs 2 a r g s e l s e do l e f t <− u n p a c k e r $ a r g s ! ! 0 r i g h t <− u n p a c k e r $ a r g s ! ! 1 return $ Bool $ l e f t ‘ op ‘ r i g h t numBoolBinop = b o o l B i n o p unpackNum strBoolBinop = boolBinop unpackStr b o o l B o o l B i n o p = b o o l B i n o p unpackBool 155 150 160 unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null parsed then t h r o w E r r o r $ TypeMismatch " n u m b e r " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( L i s t [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " n u m b e r " notNum unpackStr unpackStr unpackStr unpackStr unpackStr : : L i s p V a l −> ThrowsError String ( String s ) = return s ( Number s ) = return $ show s ( Bool s ) = return $ show s n o t S t r i n g = t h r o w E r r o r $ TypeMismatch " s t r i n g " n o t S t r i n g 165 170 unpackBool : : L i s p V a l −> ThrowsError Bool unpackBool ( Bool b ) = return b unpackBool n o t B o o l = t h r o w E r r o r $ TypeMismatch " b o o l e a n " n o t B o o l 175 car car car car car cdr cdr cdr cdr cdr cdr cons cons cons cons cons cons eqv eqv eqv eqv eqv eqv : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return x [ DottedList ( x : xs ) ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return $ L i s t x s [ DottedList ( : x s ) x ] = return $ D o t t e d L i s t x s x [ D o t t e d L i s t [ x s ] x ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ x1 . x2 ) = case eqv [ x1 . ( D o t t e d L i s t y s y ) ] = eqv [ L i s t $ x s ++ [ x ] . x2 ] o f Left e r r −> False Right ( Bool v a l ) −> v a l eqv [ . L i s t [ ] ] = return $ L i s t [ x1 ] [ x . Eq a = > AnyUnpacker ( L i s p V a l −> ThrowsError a ) . x2 ] = return $ D o t t e d L i s t [ x1 ] x2 b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t 180 185 190 195 200 205 : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ ( Bool a r g 1 ) . D o t t e d L i s t x s x l a s t ] = return $ D o t t e d L i s t ( [ x ] ++ x s ) x l a s t [ x1 .

a r g 2 ] return $ Bool $ ( p r i m i t i v e E q u a l s | | l e t ( Bool x ) = e q v E q u a l s in x ) e q u a l b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t data L i s p E r r o r = | | | | | | sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l NotFunction String String UnboundVar String String D e f a u l t String 220 225 230 235 240 : : L i s p E r r o r −> String ( UnboundVar message varname ) = message ++ " : " ++ varname ( BadSpecialForm message form ) = message ++ " : " ++ show form ( NotFunction message f u n c ) = message ++ " : " ++ show f u n c ( NumArgs e x p e c t e d f o u nd ) = " E x p e c t e d " ++ show e x p e c t e d ++ " a r g s : f o u n d v a l u e s " ++ u n w o r d s L i s t f o und sh ow E r r o r ( TypeMismatch e x p e c t e d f o u nd ) = " I n v a l i d t y p e : e x p e c t e d " ++ expected ++ " . show ) 255 e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l flushStr flushStr : : String −> IO ( ) s t r = putStr s t r >> hFlush stdout 260 readPrompt : : String −> IO String readPrompt prompt = f l u s h S t r prompt >> getLine e v a l A n d P r i n t : : Env −> String −> IO ( ) e v a l A n d P r i n t env e x p r = e v a l S t r i n g env e x p r > >= putStrLn 265 e v a l S t r i n g : : Env −> String −> IO String e v a l S t r i n g env e x p r = runIOThrows $ liftM show $ ( l i f t T h r o w s $ readExpr expr ) > >= e v a l env 270 until : : Monad m = > ( a −> Bool ) −> m a −> ( a −> m ( ) ) −> m ( ) until pred prompt a c t i o n = do r e s u l t <− prompt i f pred r e s u l t then return ( ) . f o u n d " ++ show f o und sh ow E r r o r ( P a r s e r p a r s e E r r ) = " P a r s e e r r o r a t " ++ show p a r s e E r r instance Show L i s p E r r o r where show = s h o w E r r o r 245 instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " A n e r r o r h a s o c c u r r e d " st r M sg = D e f a u l t 250 type ThrowsError = Either L i s p E r r o r t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . AnyUnpacker unpackBool ] e q v E q u a l s <− eqv [ arg1 .74 210 CHAPTER 7. ADDING VARIABLES AND ASSIGNMENT 215 unpa ck Equal s : : L i s p V a l −> L i s p V a l −> Unpacker −> ThrowsError Bool unpa ck Equal s a r g 1 a r g 2 ( AnyUnpacker u n p a c k e r ) = do unpacked1 <− u n p a c k e r a r g 1 unpacked2 <− u n p a c k e r a r g 2 return $ unpacked1 == unpacked2 ‘ c a t c h E r r o r ‘ ( const $ return False ) e q u a l : : [ L i s p V a l ] −> ThrowsError L i s p V a l e q u a l [ arg1 . a r g 2 ] = do p r i m i t i v e E q u a l s <− liftM or $ mapM ( unpa ckEqual s a r g 1 a r g 2 ) [ AnyUnpacker unpackNum . AnyUnpacker unpackStr .

type Env = IORef [ ( String . / l i s p Lisp>>> ( d e f i n e x 3 ) . ( f l i p writeIORef value ) ) ( lookup v a r env ) return v a l u e d e f i n e V a r : : Env −> String −> L i s p V a l −> IOThrowsError L i s p V a l d e f i n e V a r envRef v a r v a l u e = do a l r e a d y D e f i n e d <− l i f t I O $ isBound envRef v a r i f alreadyDefined then s e t V a r envRef v a r v a l u e >> return v a l u e e l s e l i f t I O $ do v a l u e R e f <− newIORef v a l u e env <− r e a d I O R e f envRef w r i t e I O R e f envRef ( ( var . r e f ) 305 310 315 320 325 And we can compile and test our program: user>> ghc − package p a r s e c −o l i s p v a r i a b l e p a r s e r . maybe False ( const True ) . IORef L i s p V a l ) ] 285 n u l l E n v : : IO Env n u l l E n v = newIORef [ ] type IOThrowsError = ErrorT L i s p E r r o r IO 290 l i f t T h r o w s : : ThrowsError a −> IOThrowsError a l i f t T h r o w s ( Left e r r ) = t h r o w E r r o r e r r l i f t T h r o w s ( Right v a l ) = return v a l runIOThrows : : IOThrowsError String −> IO String runIOThrows a c t i o n = runErrorT ( t r a p E r r o r a c t i o n ) > >= return . v a l u e ) = do r e f <− newIORef v a l u e return ( var .75 else action 275 r e s u l t >> u n t i l pred prompt a c t i o n runOne : : String −> IO ( ) runOne e x p r = n u l l E n v > >= f l i p e v a l A n d P r i n t e x p r 280 runRepl : : IO ( ) runRepl = n u l l E n v > >= u n t i l evalAndPrint (== " q u i t " ) ( readPrompt " L i s p > > > " ) . lookup v a r 300 g e t V a r : : Env −> String −> IOThrowsError L i s p V a l g e t V a r envRef v a r = do env <− l i f t I O $ r e a d I O R e f envRef maybe ( t h r o w E r r o r $ UnboundVar " G e t t i n g a n unbound variable " var ) ( l i f t I O . L i s p V a l ) ] −> IO Env b i n d V a r s envRef b i n d i n g s = r e a d I O R e f envRef > >= extendEnv b i n d i n g s > >= newIORef where extendEnv b i n d i n g s env = liftM (++ env ) (mapM a ddBindin g bindings ) addBi nding ( var . readIORef ) ( lookup v a r env ) s e t V a r : : Env −> String −> L i s p V a l −> IOThrowsError L i s p V a l s e t V a r envRef v a r v a l u e = do env <− l i f t I O $ r e a d I O R e f envRef maybe ( t h r o w E r r o r $ UnboundVar " S e t t i n g a n unbound variable " var ) ( l i f t I O . extractValue 295 isBound : : Env −> String −> IO Bool isBound envRef v a r = r e a d I O R e f envRef > >= return . hs user> ># . v a l u e R e f ) : env ) return v a l u e b i n d V a r s : : Env −> [ ( String .

76 CHAPTER 7. ADDING VARIABLES AND ASSIGNMENT 3 Lisp>>> (+ x 2 ) 5 Lisp>>> (+ y 2 ) G e t t i n g an unbound v a r i a b l e : y Lisp>>> ( d e f i n e y 5 ) 5 Lisp>>> (+ x (− y 2 ) ) 6 Lisp>>> ( d e f i n e s t r "A string " ) "A string " Lisp>>> (< s t r " The string " ) I n v a l i d type : e x p e c t e d number . found "A string " Lisp>>> ( s t r i n g <? s t r " The string " ) #t .

because we’d like to be able to store +. in variables and pass them to functions. the names of the parameters. so we’re only using them for demonstration purposes. the function body. eqv?. they can be invaluable in large-scale programming. whether the function accepts a variable-length list of arguments. The PrimitiveFunc constructor stores a function that takes a list of arguments to a ThrowsError LispVal. body : : [ L i s p V a l ] . Records are somewhat clumsy in Haskell. as a list of expressions 4. After this section. Next. and if so. the variable name it’s bound to 3. Our implementation is nearly finished. However. etc. as they’re bound in the function body 2.Chapter 8 Defining Scheme Functions: Closures and Environments Now that we can define variables. c l o s u r e : : Env } We’ve added a separate constructor for primitives. We store 4 pieces of information: 1. we’ll want to edit our show function to include the new types: 77 . the environment that the function was created in This is an example of a record type. you’ll be able to define your own functions within Scheme and use them from other functions. we might as well extend it to functions. We also want a constructor to store user-defined functions. Let’s start by defining new LispVal constructors: 33 • live version • discussion • edit • comment • report an error 34 35 | P r i m i t i v e F u n c ( [ L i s p V a l ] −> ThrowsError L i s p V a l ) | Func { params : : [ String ] . the same type that is stored in our primitive list. v a r a r g : : ( Maybe String ) .

length evalBody env = liftM l a s t $ mapM ( e v a l env ) body bindVarArgs a r g env = case a r g of Just argName −> l i f t I O $ bindVars env [ ( argName . " ++ a r g ) ++ ") . Field names come first and the variables they’ll be bound to come afterwards. it’ll be passed a LispVal representing the actual function. Next. Records let you pattern match on both the field name (as shown above) or the field position. we take that and the function’s closure (not the current environment—this is what gives us lexical scoping) and use them to create a new environment to evaluate the function in. body = body . DEFINING SCHEME FUNCTIONS 90 91 92 93 showVal ( P r i m i t i v e F u n c ) = " < primitive >" showVal ( Func { params = a r g s . The result is of type IO. . The first thing we do is zip the list of parameter names and the list of (already-evaluated) argument values together into a list of pairs. we just print out the word "<primitive>" for primitives and the header info for user-defined functions.78 88 89 CHAPTER 8.. 131 132 a p p l y : : L i s p V a l −> [ L i s p V a l ] −> IOThrowsError L i s p V a l apply ( PrimitiveFunc func ) args = l i f t T h r o w s $ func args The interesting code happens when we’re faced with a user defined function. Then. This is an example of pattern-matching for records: as with normal algebraic types. c l o s u r e = env } ) = "( lambda (" ++ unwords (map show a r g s ) ++ ( case v a r a r g s of Nothing −> "" Just a r g −> " .) " Instead of showing the full function. It throws an error if they don’t match. we need to change apply. so we’ll use the latter form: 133 134 135 136 137 138 139 140 141 142 a p p l y ( Func params v a r a r g s body c l o s u r e ) a r g s = i f num params /= num a r g s && v a r a r g s == Nothing then t h r o w E r r o r $ NumArgs (num params ) a r g s e l s e ( l i f t I O $ bindVars c l o s u r e $ zip params a r g s ) >>= bindVarArgs v a r a r g s >>= evalBody where r e m a i n i n g A r g s = drop ( length params ) a r g s num = toInteger . We define a local function num to enhance readability and make the program a bit shorter.. so we need to liftIO it into the combined monad. L ist $ r e m a i n i n g A r g s ) ] Nothing −> return env The very first thing this function does is check the length of the parameter list against the expected number of arguments. v a r a r g = v a r a r g s . we do the bulk of the call in monadic pipeline that binds the arguments to a new environment and executes the statements in the body. while the function as a whole is IOThrowsError. Instead of being passed the name of a function. that makes the code simpler: we need only read the function out of the value and apply it. For primitives. Assuming the call is valid. a pattern looks exactly like a constructor call.

e v a l A n d P r i n t (== " quit " ) ( readPrompt Finally. and then binds the new pairs into the new environment. and then returns the value of the last statement. we have to bind them when the program starts up: 320 321 322 p r i m i t i v e B i n d i n g s : : IO Env p r i m i t i v e B i n d i n g s = null Env >>= ( f l i p bindVars $ map makePrimitiveFunc p r i m i t i v e s ) where makePrimitiveFunc ( var . and pass that to bindVars. We define the local variable remainingArgs for readability. we create a singleton list that has the variable name as the key and the remaining args as the value. P r i m i t i v e F u n c func ) This takes the initial null environment. Now. We’ll start by creating a few helper functions to make it a little easier to create function objects in the IOThrowsError monad: 367 368 369 makeFunc v a r a r g s env params body = return $ Func (map showVal params ) v a r a r g s body env makeNormalFunc = makeFunc Nothing makeVarargs = makeFunc . Since we’re now storing primitives as regular values in variables. We use the local function evalBody for this. f u n c ) = ( var .79 Now it’s time to bind the remaining arguments to the varArgs variable. Otherwise. If the function doesn’t take varArgs (the Nothing clause). using the built-in function drop to ignore all the arguments that have already been bound to variables. We also want to change runOne and runRepl to primitive Bindings instead: 309 310 311 312 313 runOne : : String −> IO ( ) runOne expr = p r i m i t i v e B i n d i n g s >>= f l i p e v a l A n d P r i n t expr runRepl : : IO ( ) runRepl = p r i m i t i v e B i n d i n g s >>= u n t i l " Lisp >>> " ) . makeNormalFunc and makeVarArgs should just be considered specializations of makeFunc with the first argument set appropriately for normal functions vs. Just . we can use them to add our extra eval clauses. The final stage is to evaluate the body in this new environment. which maps the monadic function eval env over every statement in the body. variable args. makes a bunch of name/value pairs consisting of PrimitiveFunc wrappers. we need to change the evaluator to support lambda and function define. This is a good example of how to use first-class functions to simplify code. using the local function bindVarArgs. then we just return the existing environment. They should be inserted after the define-variable clause and before the function-application one: 115 116 e v a l env ( Li st (Atom " define " : L is t (Atom var : params ) : body ) ) = makeNormalFunc env params body >>= d e f i n e V a r env var . showVal Here.

we also feed the output into defineVar to bind a variable in the local environment. they just use pattern matching to destructure the form and then call the appropriate function helper. E r r o r Data . IORef Text . c l o s u r e : : Env } Parser LispVal 25 30 35 . Environment C o n t r o l . In define’s case. The complete code is therefore: Listing 8.1: A parser able to handle functions (functionparser. P a r s e r C o m b i n a t o r s . We also need to change the function application clause to remove the liftThrows function. P a r s e c hiding ( s p a c e s ) IO hiding ( try ) 5 10 15 main : : IO ( ) main = do a r g s <− getArgs case length a r g s o f 0 −> runRepl 1 −> runOne $ a r g s ! ! 0 otherwise −> putStrLn " P r o g r a m t a k e s o n l y 0 o r 1 a r g u m e n t " symbol : : P a r s e r Char symbol = oneOf " ! $ % & | * + . Monad ./ : <= >? @ ^ _ ~ # " 20 readExpr : : String −> ThrowsError L i s p V a l readExpr i n p u t = case p a r s e p a r s e E x p r " l i s p " i n p u t o f Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l spaces : : Parser () s p a c e s = skipMany1 s p a c e data L i s p V a l = | | | | | | | parseString :: Atom String List [ LispVal ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool P r i m i t i v e F u n c ( [ L i s p V a l ] −> ThrowsError L i s p V a l ) Func { params : : [ String ] .80 117 CHAPTER 8. DEFINING SCHEME FUNCTIONS 118 119 120 121 122 123 124 125 126 127 128 e v a l env ( Li s t (Atom " define " : D o t t e d L i s t (Atom var : params ) v a r a r g s : body ) ) = makeVarargs v a r a r g s env params body >>= d e f i n e V a r env var e v a l env ( Li s t (Atom " lambda " : L ist params : body ) ) = makeNormalFunc env params body e v a l env ( Li s t (Atom " lambda " : D o t t e d L i s t params v a r a r g s : body ) ) = makeVarargs v a r a r g s env params body e v a l env ( Li s t (Atom " lambda " : varargs@ (Atom ) : body ) ) = makeVarargs v a r a r g s env [ ] body e v a l env ( Li s t ( f u n c t i o n : a r g s ) ) = do f u n c <− e v a l env f u n c t i o n a r g V a l s <− mapM ( e v a l env ) a r g s apply func argVals As you can see.hs) module import import import import import import Main where Monad System . since apply now works in the IOThrowsError monad. body : : [ L i s p V a l ] . v a r a r g : : ( Maybe String ) .

81 p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " \ " " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom o f " # t " −> Bool True " # f " −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number . read ) $ many1 d i g i t 55 40 45 50 p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L i s t $ sepBy p a r s e E x p r s p a c e s parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy p a r s e E x p r s p a c e s t a i l <− c h a r ’ . body = body . map showVal instance Show L i s p V a l where show = showVal 60 65 70 75 80 85 90 95 100 e v a l : : Env −> L i s p V a l −> IOThrowsError L i s p V a l e v a l env val@ ( String ) = return v a l e v a l env val@ ( Number ) = return v a l e v a l env val@ ( Bool ) = return v a l . ) " u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords . " ++ showVal t a i l ++ " ) " showVal ( P r i m i t i v e F u n c ) = " < primitive >" showVal ( Func { params = a r g s . " ++ a r g ) ++ " ) . v a r a r g = v a r a r g s . x ] parseExpr : : Parser LispVal p a r s e E x p r = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e Q u o t e d <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x showVal : : L i s p V a l −> String showVal ( String c o n t e n t s ) = " \ " " ++ c o n t e n t s ++ " \ " " showVal ( Atom name ) = name showVal ( Number c o n t e n t s ) = show c o n t e n t s showVal ( Bool True ) = " # t " showVal ( Bool False ) = " # f " showVal ( L i s t c o n t e n t s ) = " ( " ++ u n w o r d s L i s t c o n t e n t s ++ " ) " showVal ( D o t t e d L i s t head t a i l ) = " ( " ++ u n w o r d s L i s t head ++ " . . ’ >> s p a c e s >> p a r s e E x p r return $ D o t t e d L i s t head t a i l parseQuoted : : Parser LispVal p a r s e Q u o t e d = do char ’\ ’ ’ x <− p a r s e E x p r return $ L i s t [ Atom " q u o t e " . . c l o s u r e = env } ) = " ( l a m b d a ( " ++ unwords (map show a r g s ) ++ ( case v a r a r g s o f Nothing −> " " Just a r g −> " .

b o o l B o o l B i n o p (&&) ) . [ L i s p V a l ] −> ThrowsError L i s p V a l ) ] p r i m i t i v e s = [ ( " + " . boolBoolBinop ( | | ) ) . numericBinop ( − ) ) . s t r B o o l B i n o p ( >) ) . s t r B o o l B i n o p (>=) ) . ( " & & " . c o n s e q . . numBoolBinop (<=) ) . length evalBody env = liftM l a s t $ mapM ( e v a l env ) body bindVarArgs a r g env = case a r g o f Just argName −> l i f t I O $ b i n d V a r s env [ ( argName . ( " <= " . ( " || " . numericBinop (+) ) . form ] ) = e v a l env form > >= d e f i n e V a r env v a r e v a l env ( L i s t ( Atom " d e f i n e " : L i s t ( Atom v a r : params ) : body ) ) = makeNormalFunc env params body > >= d e f i n e V a r env v a r e v a l env ( L i s t ( Atom " d e f i n e " : D o t t e d L i s t ( Atom v a r : params ) v a r a r g s body ) ) = makeVarargs v a r a r g s env params body > >= d e f i n e V a r env v a r e v a l env ( L i s t ( Atom " l a m b d a " : L i s t params : body ) ) = makeNormalFunc env params body e v a l env ( L i s t ( Atom " l a m b d a " : D o t t e d L i s t params v a r a r g s : body ) ) = makeVarargs v a r a r g s env params body e v a l env ( L i s t ( Atom " l a m b d a " : v a r a r g s @ ( Atom ) : body ) ) = makeVarargs v a r a r g s env [ ] body e v a l env ( L i s t ( f u n c t i o n : a r g s ) ) = do f u n c <− e v a l env f u n c t i o n a r g V a l s <− mapM ( e v a l env ) a r g s apply func argVals e v a l env badForm = t h r o w E r r o r $ BadSpecialForm " U n r e c o g n i z e d s p e c i a l f o r m " badForm apply apply apply if : : L i s p V a l −> [ L i s p V a l ] −> IOThrowsError L i s p V a l ( PrimitiveFunc func ) args = liftThrows $ func args ( Func params v a r a r g s body c l o s u r e ) a r g s = num params /= num a r g s && v a r a r g s == Nothing then t h r o w E r r o r $ NumArgs (num params ) a r g s e l s e ( l i f t I O $ b i n d V a r s c l o s u r e $ zip params a r g s ) > >= bindVarArgs v a r a r g s > >= evalBody where r e m a i n i n g A r g s = drop ( length params ) a r g s num = toInteger . ( " r e m a i n d e r " . numBoolBinop (>=) ) . ( " / = " . ( " >= " . numBoolBinop ( > ) ) . numBoolBinop (==) ) . ( " s t r i n g > = ? " . ( " < " . car ) . ( " s t r i n g < = ? " . s t r B o o l B i n o p (==) ) . ( " cons " . ( " > " . ( " = " . pred . ( " . Atom var . ( " s t r i n g ? " .82 CHAPTER 8. ( " q u o t i e n t " . numericBinop ( ∗ ) ) . numericBinop mod) . numBoolBinop (/=) ) . s t r B o o l B i n o p (<=) ) . ( " s t r i n g = ? " . cdr ) . a l t ] ) = do r e s u l t <− e v a l env pred case r e s u l t o f Bool False −> e v a l env a l t otherwise −> e v a l env c o n s e q e v a l env ( L i s t [ Atom " s e t ! " . ( " / " . ( " m o d " . L i s t $ remainingArgs ) ] Nothing −> return env : 130 135 140 145 150 155 160 165 p r i m i t i v e s : : [ ( String . cons ) . Atom var . numBoolBinop ( < ) ) . numericBinop div ) . DEFINING SCHEME FUNCTIONS 105 110 115 120 125 e v a l env ( Atom id ) = g e t V a r env id e v a l env ( L i s t [ Atom " q u o t e " . v a l ] ) = return v a l e v a l env ( L i s t [ Atom " i f " . ( " cdr " . numericBinop rem ) . form ] ) = e v a l env form > >= s e t V a r env v a r e v a l env ( L i s t [ Atom " d e f i n e " . ( " car " ." . numericBinop quot ) . ( " * " .

Number . 170 175 180 b o o l B i n o p : : ( L i s p V a l −> ThrowsError a ) −> ( a −> a −> Bool ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l b o o l B i n o p u n p a c k e r op a r g s = i f length a r g s /= 2 then t h r o w E r r o r $ NumArgs 2 a r g s e l s e do l e f t <− u n p a c k e r $ a r g s ! ! 0 r i g h t <− u n p a c k e r $ a r g s ! ! 1 return $ Bool $ l e f t ‘ op ‘ r i g h t numBoolBinop = b o o l B i n o p unpackNum strBoolBinop = boolBinop unpackStr b o o l B o o l B i n o p = b o o l B i n o p unpackBool unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null parsed then t h r o w E r r o r $ TypeMismatch " n u m b e r " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( L i s t [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " n u m b e r " notNum unpackStr unpackStr unpackStr unpackStr unpackStr : : L i s p V a l −> ThrowsError String ( String s ) = return s ( Number s ) = return $ show s ( Bool s ) = return $ show s n o t S t r i n g = t h r o w E r r o r $ TypeMismatch " s t r i n g " n o t S t r i n g 185 190 195 200 205 unpackBool : : L i s p V a l −> ThrowsError Bool unpackBool ( Bool b ) = return b unpackBool n o t B o o l = t h r o w E r r o r $ TypeMismatch " b o o l e a n " n o t B o o l car car car car car cdr cdr cdr cdr cdr cdr cons cons cons cons cons cons eqv eqv eqv eqv eqv : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return x [ DottedList ( x : xs ) ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ L i s t ( x : x s ) ] = return $ L i s t x s [ DottedList ( : x s ) x ] = return $ D o t t e d L i s t x s x [ D o t t e d L i s t [ x s ] x ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " p a i r " badArg b a d A r g L i s t = t h r o w E r r o r $ NumArgs 1 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ x1 . ( " e q v ? " . L i s t [ ] ] = return $ L i s t [ x1 ] [ x . D o t t e d L i s t x s x l a s t ] = return $ D o t t e d L i s t ( [ x ] ++ x s ) x l a s t [ x1 . ( Atom a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 210 215 220 225 230 . ( " equal ?" . eqv ) .83 ( " e q ? " . ( String a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Atom a r g 1 ) . x2 ] = return $ D o t t e d L i s t [ x1 ] x2 b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ ( Bool a r g 1 ) . eqv ) . L i s t x s ] = return $ L i s t $ [ x ] ++ x s [ x . equal ) ] numericBinop : : ThrowsError numericBinop op numericBinop op f o l d l 1 op ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> LispVal s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 s i n g l e V a l params = mapM unpackNum params > >= return . ( Bool a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( Number a r g 1 ) . ( Number a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 [ ( String a r g 1 ) .

f o u n d " ++ show f o und sh ow E r r o r ( P a r s e r p a r s e E r r ) = " P a r s e e r r o r a t " ++ show p a r s e E r r instance Show L i s p E r r o r where show = s h o w E r r o r 280 instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " A n e r r o r h a s o c c u r r e d " st r M sg = D e f a u l t type ThrowsError = Either L i s p E r r o r 285 t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . x2 ) = case eqv [ x1 . a r g 2 ] = do p r i m i t i v e E q u a l s <− liftM or $ mapM ( unpa ckEqual s a r g 1 a r g 2 ) [ AnyUnpacker unpackNum . AnyUnpacker unpackBool ] e q v E q u a l s <− eqv [ arg1 . Eq a = > AnyUnpacker ( L i s p V a l −> ThrowsError a ) unpa ck Equal s : : L i s p V a l −> L i s p V a l −> Unpacker −> ThrowsError Bool unpa ck Equal s a r g 1 a r g 2 ( AnyUnpacker u n p a c k e r ) = do unpacked1 <− u n p a c k e r a r g 1 unpacked2 <− u n p a c k e r a r g 2 return $ unpacked1 == unpacked2 ‘ c a t c h E r r o r ‘ ( const $ return False ) e q u a l : : [ L i s p V a l ] −> ThrowsError L i s p V a l e q u a l [ arg1 . show ) e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l 290 flushStr flushStr : : String −> IO ( ) s t r = putStr s t r >> hFlush stdout :: String −> IO String readPrompt . DEFINING SCHEME FUNCTIONS 235 eqv [ ( D o t t e d L i s t x s x ) .84 CHAPTER 8. L i s t $ y s ++ [ y ] ] eqv [ ( L i s t a r g 1 ) . ( D o t t e d L i s t y s y ) ] = eqv [ L i s t $ x s ++ [ x ] . ] = return $ Bool False eqv b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t data Unpacker = f o r a l l a . x2 ] o f Left e r r −> False Right ( Bool v a l ) −> v a l eqv [ . AnyUnpacker unpackStr . a r g 2 ] return $ Bool $ ( p r i m i t i v e E q u a l s | | l e t ( Bool x ) = e q v E q u a l s in x ) e q u a l b a d A r g L i s t = t h r o w E r r o r $ NumArgs 2 b a d A r g L i s t data L i s p E r r o r = | | | | | | sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r sh ow E r r o r NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l NotFunction String String UnboundVar String String D e f a u l t String 240 245 250 255 260 265 270 275 : : L i s p E r r o r −> String ( UnboundVar message varname ) = message ++ " : " ++ varname ( BadSpecialForm message form ) = message ++ " : " ++ show form ( NotFunction message f u n c ) = message ++ " : " ++ show f u n c ( NumArgs e x p e c t e d f o u nd ) = " E x p e c t e d " ++ show e x p e c t e d ++ " a r g s : f o u n d v a l u e s " ++ u n w o r d s L i s t f o und sh ow E r r o r ( TypeMismatch e x p e c t e d f o u nd ) = " I n v a l i d t y p e : e x p e c t e d " ++ expected ++ " . ( L i s t a r g 2 ) ] = return $ Bool $ ( length a r g 1 == length a r g 2 ) && ( and $ map e q v P a i r $ zip a r g 1 a r g 2 ) where e q v P a i r ( x1 .

85 readPrompt prompt = f l u s h S t r prompt >> getLine 295 e v a l A n d P r i n t : : Env −> String −> IO ( ) e v a l A n d P r i n t env e x p r = e v a l S t r i n g env e x p r > >= putStrLn 300 e v a l S t r i n g : : Env −> String −> IO String e v a l S t r i n g env e x p r = runIOThrows $ liftM show $ ( l i f t T h r o w s $ readExpr expr ) > >= e v a l env until : : Monad m = > ( a −> Bool ) −> m a −> ( a −> m ( ) ) −> m ( ) until pred prompt a c t i o n = do r e s u l t <− prompt i f pred r e s u l t then return ( ) e l s e a c t i o n r e s u l t >> u n t i l pred prompt a c t i o n runOne : : String −> IO ( ) runOne e x p r = p r i m i t i v e B i n d i n g s > >= f l i p e v a l A n d P r i n t e x p r runRepl : : IO ( ) runRepl = p r i m i t i v e B i n d i n g s > >= u n t i l " ) . readIORef ) ( lookup v a r env ) s e t V a r : : Env −> String −> L i s p V a l −> IOThrowsError L i s p V a l s e t V a r envRef v a r v a l u e = do env <− l i f t I O $ r e a d I O R e f envRef maybe ( t h r o w E r r o r $ UnboundVar " S e t t i n g a n unbound variable " var ) ( l i f t I O . evalAndPrint (== " q u i t " ) ( readPrompt " L i s p > > > 305 310 315 type Env = IORef [ ( String . f u n c ) = ( var . lookup v a r g e t V a r : : Env −> String −> IOThrowsError L i s p V a l g e t V a r envRef v a r = do env <− l i f t I O $ r e a d I O R e f envRef maybe ( t h r o w E r r o r $ UnboundVar " G e t t i n g a n unbound variable " var ) ( l i f t I O . IORef L i s p V a l ) ] n u l l E n v : : IO Env n u l l E n v = newIORef [ ] 320 p r i m i t i v e B i n d i n g s : : IO Env p r i m i t i v e B i n d i n g s = nullEnv > >= ( f l i p b i n d V a r s $ map m a k e Pr i m i t i v e F u n c primitives ) where m a k e Pr i m i t i v e F u n c ( var . P r i m i t i v e F u n c f u n c ) type IOThrowsError = ErrorT L i s p E r r o r IO l i f t T h r o w s : : ThrowsError a −> IOThrowsError a l i f t T h r o w s ( Left e r r ) = t h r o w E r r o r e r r l i f t T h r o w s ( Right v a l ) = return v a l 325 330 runIOThrows : : IOThrowsError String −> IO String runIOThrows a c t i o n = runErrorT ( t r a p E r r o r a c t i o n ) > >= return . maybe False ( const True ) . ( f l i p writeIORef value ) ) ( lookup v a r env ) return v a l u e d e f i n e V a r : : Env −> String −> L i s p V a l −> IOThrowsError L i s p V a l d e f i n e V a r envRef v a r v a l u e = do a l r e a d y D e f i n e d <− l i f t I O $ isBound envRef v a r i f alreadyDefined then s e t V a r envRef v a r v a l u e >> return v a l u e 340 345 350 . extractValue 335 isBound : : Env −> String −> IO Bool isBound envRef v a r = r e a d I O R e f envRef > >= return .

. v a l u e R e f ) : env ) return v a l u e 360 365 b i n d V a r s : : Env −> [ ( String . ) Lisp>>> ( f a c t o r i a l 1 0 ) 3628800 Lisp>>> ( d e f i n e ( c o u n t e r i n c ) ( lambda ( x ) ( s e t ! i n c (+ x i n c ) ) inc ) ) ( lambda ( " inc " ) . . r e f ) makeFunc v a r a r g s env params body = return $ Func (map showVal params ) v a r a r g s body env makeNormalFunc = makeFunc Nothing makeVarargs = makeFunc . . hs user>> . DEFINING SCHEME FUNCTIONS e l s e l i f t I O $ do v a l u e R e f <− newIORef v a l u e env <− r e a d I O R e f envRef w r i t e I O R e f envRef ( ( var . ) Lisp>>> ( d e f i n e my−count ( c o u n t e r 5 ) ) ( lambda ( "x" ) . Just . . / l i s p Lisp>>> ( d e f i n e ( f x y ) (+ x y ) ) ( lambda ( "x" "y" ) .86 355 CHAPTER 8. showVal We can now compile and run our program. L i s p V a l ) ] −> IO Env b i n d V a r s envRef b i n d i n g s = r e a d I O R e f envRef > >= extendEnv b i n d i n g s > >= newIORef where extendEnv b i n d i n g s env = liftM (++ env ) (mapM a ddBindin g bindings ) addBi ndi ng ( var . . ) Lisp>>> (my−count 3 ) 8 Lisp>>> (my−count 6 ) 14 Lisp>>> (my−count 5 ) 19 . v a l u e ) = do r e f <− newIORef v a l u e return ( var . . . and use it to write real programs! user>> ghc − package p a r s e c − f g l a s g o w − e x t s −o l i s p f u n c t i o n p a r s e r . . ) Lisp>>> ( f 1 2 ) 3 Lisp>>> ( f 1 2 3 ) Expected 2 a r g s : found v a l u e s 1 2 3 Lisp>>> ( f 1 ) Expected 2 a r g s : found v a l u e s 1 Lisp>>> ( d e f i n e ( f a c t o r i a l x ) ( i f (= x 1 ) 1 ( ∗ x ( f a c t o r i a l (− x 1) ) ) ) ) ( lambda ( "x" ) .

it gets really tedious typing in functions every time we start the interpreter. we need to add a parser that will support several expressions. so it would be nice to load files of code and execute them. separated by whitespace. For completeness.Chapter 9 Creating IO Primitives: File I/O Our Scheme can’t really communicate with the outside world yet. we ought to provide showVal methods for the new data types: 90 91 | Port Handle showVal ( Port ) = " <IO port >" showVal ( IOFunc ) = " <IO primitive >" This’ll let the REPL function properly and not crash when you use a function that returns a port. We’ll need to make some minor changes to our parser to support load. that you can read and write to. so it would be nice if we could give it some IO functions. We want a dedicated constructor for primitive functions that perform IO: 35 • live version • discussion • edit • comment • report an error While we’re at it. so they can’t perform any IO. Most of our IO functions will take one of these to read from or write to: 33 | IOFunc ( [ L i s p V a l ] −> IOThrowsError L i s p V a l ) A Handle is basically the Haskell notion of a port: it’s an opaque data type. PrimitiveFuncs have a specific type signature that doesn’t include the IO monad. returned from openFile and similar IO actions. Also. The first thing we’ll need is a new constructor for LispVals. Since Scheme files usually contain several definitions. let’s also define a constructor for the Scheme data type of a port. We can re-use much of the existing infrastructure by factoring our basic readExpr so that it takes the actual parser as a parameter: 87 . And it also needs to handle errors.

88 16 17 18 19 20 21 22 CHAPTER 9. f u n c ) = ( var . we can’t use the existing primitive list because lists cannot contain elements of different types. We’ll be using readExpr in our REPL to read single expressions. ( " close . applyP roc ) . makePort ReadMode ) . c l o s e P o r t ) . L is t a r g s ] = a pply f u n c a r g s appl yPro c ( f u n c : a r g s ) = a pply f u n c a r g s . r e a d A l l ) ] The only difference here is in the type signature.file " . we’ll be using readExprList from within load to read programs. makePort WriteMode ) . structured just like the existing primitive list: 378 379 380 381 382 383 384 385 386 387 i o P r i m i t i v e s : : [ ( String .output . ( " write " . ( " read " .file " . think of both readExpr and readExprList as specializations of the newly-renamed readOrThrow. r e a d P r o c ) . applyProc is a very thin wrapper around apply.all " . ( " open .port " . we’ll want a new list of IO primitives. CREATING IO PRIMITIVES readOrThrow : : P a r s e r a −> String −> ThrowsError a readOrThrow p a r s e r i n p u t = case p a r s e p a r s e r " lisp " i n p u t of Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l readExpr = readOrThrow parseExpr r e a d E x p r L i s t = readOrThrow ( endBy parseExpr s p a c e s ) Again.input .port " . [ L i s p V a l ] −> IOThrowsError L i s p V a l ) ] i o P r i m i t i v e s = [ ( " apply " . Unfortunately. ( " close . ( " open . Now we start defining the actual functions.input .contents " . ( " read . r e a d C o n t e n t s ) . We also need to change the definition of primitiveBindings to add our new primitives: 327 328 329 p r i m i t i v e B i n d i n g s : : IO Env p r i m i t i v e B i n d i n g s = nullE nv >>= ( f l i p bindVars $ map ( makeFunc IOFunc ) i o P r i m i t i v e s ++ map ( makeFunc PrimitiveFunc ) primitives ) where makeFunc c o n s t r u c t o r ( var . responsible for destructuring the argument list into the form apply expects: 389 390 391 appl yPro c : : [ L i s p V a l ] −> IOThrowsError L i s p V a l appl yPro c [ func .output . Next. ( " read . c l o s e P o r t ) . w r i t e P r o c ) . and now call it on the list of ioPrimitives in addition to the plain old primitives. c o n s t r u c t o r func ) 330 We’ve generalized makeFunc to take a constructor argument.

Port stdout ] [ obj . writeProc converts a LispVal to a string and then writes it out on the specified port: 404 405 406 writeProc writeProc writeProc return : : [ L i s p V a l ] −> IOThrowsError L i s p V a l [ o b j ] = w r i t e P r o c [ obj . so they both need to be converted (with liftIO and liftThrows. because hPrint takes a value of type Show a. readContents reads the whole file into a string in memory. This is why we bothered making LispVal an instance of Show. ReadMode for open-input-file and WriteMode for open-output-file: 393 394 makePort : : IOMode −> [ L i s p V a l ] −> IOThrowsError L i s p V a l makePort mode [ String f i l e n a m e ] = liftM Port $ l i f t I O $ openFile f i l e n a m e mode closePort also wraps the equivalent Haskell procedure. we wouldn’t be able to use this automatic conversion and would have to call showVal ourselves. it’s responsible only for reading and parsing a file full of . it could save us significant labor. Port p o r t ] = l i f t I O $ hPrint p o r t o b j >> ( $ Bool True ) We don’t have to explicitly call show on the object we’re printing. respectively) to the IOThrowsError monad.89 makePort wraps the Haskell function openFile. otherwise. Rather. It’s a thin wrapper around Haskell’s readFile. Only then can they be piped together with the monadic bind operator. so if we’d extended this with other IO primitives. Many other Haskell functions also take instances of Show. this time hClose: 396 397 398 c l o s e P o r t : : [ L i s p V a l ] −> IOThrowsError L i s p V a l c l o s e P o r t [ Port p o r t ] = l i f t I O $ hClose p o r t >> ( return $ Bool True ) closePort = return $ Bool False readProc (named to avoid a name conflict with the built-in read) wraps the Haskell hGetLine and then sends the result to parseExpr. readExpr Notice how hGetLine is of type IO String yet readExpr is of type String -> ThrowsError LispVal. to be turned into a LispVal suitable for Scheme: 400 401 402 r e a d P r o c : : [ L i s p V a l ] −> IOThrowsError L i s p V a l r e a d P r o c [ ] = r e a d P r o c [ Port stdin ] r e a d P r o c [ Port p o r t ] = ( l i f t I O $ hGetLine p o r t ) >>= l i f t T h r o w s . again just lifting the IO action into an IOThrowsError action and wrapping it in a String constructor: 408 409 r e a d C o n t e n t s : : [ L i s p V a l ] −> IOThrowsError L i s p V a l r e a d C o n t e n t s [ String f i l e n a m e ] = liftM String $ l i f t I O $ readFile f i l e n a m e The helper function load doesn’t do what Scheme’s load does (we handle that later). It’s calling show for us automatically. It’s intended to be partially-applied to the IOMode. converting it to the right type and wrapping its return value in the Port constructor.

with any error messages going to STDERR. which generally has no meaning to anything. we’ll also be printing the return value of the last statement in the program.90 CHAPTER 9. Then we print the result on STDERR.) Then. we have to do this before catching errors. and evaluates it. 411 412 l o a d : : String −> IOThrowsError [ L i s p V a l ] l o a d f i l e n a m e = ( l i f t I O $ readFile f i l e n a m e ) >>= l i f t T h r o w s . (Traditional UNIX conventions hold that STDOUT should be used only or program output. so let’s go through it step-by-step. it creates a Scheme form (load "arg1"). doesn’t take an environment argument. String f i l e n a m e ] ) = l o a d f i l e n a m e >>= liftM l a s t . L ist $ map String $ drop 1 a r g s ) ] ( runIOThrows $ liftM show $ e v a l env ( L is t [ Atom " load " . because the error handler converts them to strings and the types must match) and then we run the whole IOThrowsError action. just as if the user had typed it in. we can simplify it to an if statement: 9 10 11 main : : IO ( ) main = do a r g s <− getArgs i f nu ll a r g s then runRepl e l s e runOne $ a r g s . We get around this by implementing load as a special form: 129 130 e v a l env ( Li s t [ Atom " load " . Since we no longer need a third clause to handle the wrong number of command-line arguments. (The first argument is the filename to execute. Apply. readExprList readAll then just wraps that return value with the List constructor: 414 415 r e a d A l l : : [ L i s p V a l ] −> IOThrowsError L i s p V a l r e a d A l l [ String f i l e n a m e ] = liftM L ist $ l o a d f i l e n a m e Implementing the actual Scheme load function is a little tricky. however. Additional command-line arguments will get bound into a list args within the Scheme program: 313 314 315 316 317 runOne : : [ String ] −> IO ( ) runOne a r g s = do env <− p r i m i t i v e B i n d i n g s >>= f l i p bindVars [ ( " args " . mapM ( e v a l env ) Finally. CREATING IO PRIMITIVES statements. because load can introduce bindings into the local environment.) Then we change main so it uses our new runOne function. and so there’s no way for a primitive function (or any function) to do this. it takes the name of a file to execute and runs that as a program. passes that into bindVars. we might as well change our runOne function so that instead of evaluating a single expression from the command line. In this case. It’s used in two places: readAll (which returns a list of values) and load (which evaluates those values as Scheme expressions). String ( a r g s ! ! 0 ) ] ) ) >>= hPutStrLn stderr That’s a little involved. The first line takes the original primitive bindings. The result is transformed to a string (remember. and then adds a variable named args that’s bound to a List containing String versions of all but the first argument.

Chapter 10 Towards a Standard Library: Fold and Unfold Our Scheme is almost complete now. not and null are defined exactly as you’d expect it. This may seem completely useless—if you already have a value. o b j s ) objs ) We also want an id function. we let those higherorder functions work even if we don’t want to do anything with the value. defining each list function in terms of a recursion on lists. using if statements: ( define ( not x ) ( if x #f #t ) ) ( define ( nu ll ? o b j ) ( i f ( eqv ? o b j ’ ( ) ) #t #f ) ) • live version • discussion • edit • comment • report an error We can use the varArgs feature to define list. This style is used by the Haskell Prelude: it gives you more concise definitions. we’ll implement two primitive recursion operators (fold and unfold) and then define our whole library based on those. we’d like a library of standard list-manipulation functions that we can use to perform some common computations. By defining id. We’ll start by defining a few obvious helper functions. 91 . less room for error. but it’s still rather hard to use. and good practice using fold to capture iteration. which just returns its argument unchanged. which just returns a list of its arguments: ( define ( l i s t . Rather than using a typical Scheme implementation. At the very least. why do you need a function to return it? However. several of our algorithms expect a function that tells us what to do with a given value.

we can write our fold function. positive? and negative?. We bind the variable zero? to the function returned by curry. NIL)))) in Scheme. 2. (3 . (2 . and replaces NIL with the accumulator. ( fold + 0 ’(1 2 3 4)) = (1 + (2 + (3 + (4 + 0)))). we add curry and compose. respectively). Start with a rightassociative version to mimic the above examples: . (4 . The best way to think about fold is to picture a list in terms of its infix constructors: [1. it’d be nice to have a flip function. which work like their Haskell equivalents (partial-application and the dot operator.92 ( define ( id o b j ) obj ) CHAPTER 10. giving us an unary function that returns true if its argument is equal to zero. in case we want to pass in a function that takes its arguments in the wrong order: ( define ( f l i p f u n c ) ( lambda ( a r g 1 a r g 2 ) ( func arg2 arg1 ) ) ) Finally. With that definition. ( define ( curry f u n c a r g 1 ) ( lambda ( a r g ) ( apply func ( cons arg1 arg ) ) ) ) ( define ( compose f g ) ( lambda ( a r g ) ( f ( apply g arg ) ) ) ) We might as well define some simple library functions that appear in the Scheme standard: ( define zero ? ( curry = 0 ) ) ( define p o s i t i v e ? ( curry < 0 ) ) ( define n e g a t i v e ? ( curry > 0 ) ) ( define ( odd ? num) (= (mod num 2 ) 1 ) ) ( define ( even ? num) (= (mod num 2 ) 0 ) ) These are basically done just as you’d expect them. Next. for example. Note the usage of curry to define zero?. 3. A fold function replaces every constructor with a binary operation. we want to define a fold function that captures the basic pattern of recursion over a list. TOWARDS A STANDARD LIBRARY Similarly. So. 4] = 1:2:3:4:[] in Haskell or (1 .

Note that func takes its arguments in the opposite order from foldr. and don’t make the distinction between foldl and foldr. This means that we process the beginning first. we’ll see some cases later where we have to use foldr to get the right result. However. we apply the function to the accumulator and first element of the list. Once we reach the end of the list. We define it to be foldl. Since the right-hand operand is folded up first. In order to preserve our intuitions about commutativity of operators. we can define a couple convenience names to match typical Scheme usage: ( define f o l d f o l d l ) ( define r e d u c e f o l d ) These are just new variables bound to the existing functions: they don’t define new functions. Next. ( define ( f o l d l f u n c accum l s t ) ( i f ( nu ll ? l s t ) accum ( f o l d l f u n c ( f u n c accum ( c a r l s t ) ) ( c d r l s t ) ) ) ) This begins the same way as the right-associative version. If not. ’() . it should therefore be the left argument of our operation in foldl. then. In foldr. after you’ve finished recursing down it. it continues applying . In foldl. an initial value. the accumulator represents the rightmost value to tack onto the end of the list. Most Schemes call fold \textit{reduce} or plain old fold. replace it with the end value. Not all operations are associative. Given an unary function. instead of applying it to the first element and the result of folding the list. but the right argument in foldr.93 ( define ( f o l d r f u n c end l s t ) ( i f ( nu ll ? l s t ) end ( f u n c ( c a r l s t ) ( f o l d r f u n c end ( c d r l s t ) ) ) ) ) The structure of this function mimics our definition almost exactly. We also want a left-associative version. we then return the result that we’ve been progressively building up. with the test for null that returns the accumulator. apply the function to the car of the list and to the result of folding this function and end value down the rest of the list. it represents the completed calculation for the leftmost part of the list. you end up with a right-associative fold. there is at least one important binary operation that is not associative: cons. we want to define a function that is the opposite of fold. however. giving us left-associativity. For most associative operations like + and ∗. the two of them are completely equivalent. however. we’ll need to deliberately choose between leftand right-associative folds. If the list is null. This time. and a unary predicate. which happens to be tail-recursive and hence runs more efficiently than foldr (it doesn’t have to recurse all the way down to the end of the list before it starts building up the computation). Once we’ve got our basic folds. For all our list manipulation functions.

l s t ) ( fold ∗ 1 lst ) ) ( define ( and . you can treat that as a for-each loop and use a catamorphism to break it down into whatever state you wish to modify. the loop body becomes a function with the loop variables as its first argument and the iteration variable as its second. ( 3 . The initialization. termination. cons the result of unfolding the next value (func init ) onto the current value. unfolds are often called anamorphisms. package up all mutable variables in the loop into a data structure (records work well for this. l s t ) ( f o l d && #t l s t ) ) ( define ( or . NIL ) ) ) ) ) ( product 1 2 3 4 ) = 1 ∗ 2 ∗ 3 ∗ 4 ∗ 1 = ( f o l d ∗ 1 ’ ( 1 . every for-loop (without early exits) can be represented as a hylomorphism. We’ll start with typical sum.94 CHAPTER 10. ( 2 . l s t ) ( fold + 0 lst ) ) ( define ( product . our function structure basically matches the definition. Otherwise. then we cons a ’() onto the last value. the list. The initial state becomes the accumulator. This is essentially what generators are in Python or Icon: ( define ( l s t l i s t i n g f u n c i n i t pred ) ( i f ( pred i n i t ) ( cons i n i t ’ ( ) ) ( c o n s i n i t ( u n f o l d f u n c ( f u n c i n i t ) pred ) ) ) ) As usual. well. (# t . TOWARDS A STANDARD LIBRARY the function to the last value until the predicate is true. and the combinations of the two are often called hylomorphisms. ( 3 . The result of the fold function is the new state of all the mutable variables. To convert from a loop to a foldl. but you can also use an algebraic data type or a list). ( 4 . product. l s t ) ( f o l d | | #f l s t ) ) These all follow from the definitions: ( sum 1 2 3 4 ) = 1 + 2 + 3 + 4 + 0 = ( f o l d + 0 ’ ( 1 . NIL ) ) ) ) ) ( and #t #t #f ) = #t && #t && #f && #t = ( f o l d && #t ’(# t . They’re interesting because any for-each loop can be represented as a catamorphism. ( 2 . Similarly. building up a list as it goes along. and. or functions: ( define ( sum . ( 4 . Let’s go through a couple examples. terminating the list. and step conditions of a for-loop define an anamorphism that builds up a list of values for the iteration variable to take. NIL ) ) ) ) . If the predicate is true. folds are often called catamorphisms. (# f . and the list becomes. Then. In academic functional programming literature.

and the nil constructor with the identity element for that operator. Another way to look at this is “The length of a list is 1 + the length of the sublist to its left. num− l i s t ) ( f o l d ( lambda ( o l d new ) ( i f (< o l d new ) o l d new ) ) first num− l i s t ) ) It’s not immediately obvious what operation to fold over the list. If the new value is greater. think in terms of its definition as a loop. max and min find the maximum and minimum of their arguments. NIL ) ) ) Since all of these operators are associative. you can just flip cons so it takes its arguments in the opposite order. That gives us our initialization value: we want to start off with the leftmost variable in the list (since we’re doing a foldl). so we’ll want it to be the maximum value we’ve found so far. Reverse the operation for min. (# f . or they’re equal. it doesn’t matter whether we use foldr or foldl. (# t . The accumulator represents any state we’ve maintained over previous iterations of the loop. let’s try some more complicated operators. keep it. . Now recall that the result of the operation becomes the new accumulator at each step. That gives us both our initialization value—0—and our function—(lambda (x y) (+ x 1)). ( define ( reverse l s t ) ( f o l d ( f l i p cons ) ’ ( ) l s t ) ) The function here is fairly obvious: if you want to reverse two cons cells. return the new value. respectively: ( define (max f i r s t . We replace the cons constructor with the operator. because none of the built-ins quite qualify. If the previous value is greater. think back to fold as a representation of a foreach loop. but how do we translate that into a fold? ( define ( length l s t ) ( f o l d ( lambda ( x y ) (+ x 1 ) ) 0 lst )) Again. and we’ve got our function. Instead. However.” Let’s try something a bit trickier: reverse. The accumulator starts off at 0 and gets incremented by 1 with each iteration. How about length? We know that we can find the length of a list by counting down it. num− l i s t ) ( f o l d ( lambda ( o l d new ) ( i f (> o l d new ) o l d new ) ) first num− l i s t ) ) ( define ( min f i r s t . Next.95 ( or #t #t #f ) = #t | | #t | | #f | | #f = ( f o l d | | #f ’(# t .

NIL)))). Try it with a foldr instead of a foldl and see what you get. Its accumulator represents the first value found so far: it starts out with #f and takes on the first value that satisfies its predicate. (3 . Map applies a function to every element of a list. The rest of the functions are just various combinations of eq?/eqv?/equal? and id/car. Ordinary lists are right associative: (1 2 3 4) = (1 . Next. folded over the list with an initial value of #f. We also provide an operation that will be applied to the next value each time the predicate tests: this lets us customize mem-helper to check the value itself (for member) or only the key of the value (for assoc). you need your fold to be left associative: (reverse ’(1 2 3 4)) = (4 . We avoid finding subsequent values by testing for a non-#f value and returning the existing accumulator if it’s already set. all of which can be represented with folds.96 CHAPTER 10. so let’s factor it out: ( define (mem − h e l p e r pred op ) ( lambda ( a c c next ) ( i f ( and ( not a c c ) ( pred ( op next ) ) ) next acc ) ) ) ( define (memq o b j l s t ) ( f o l d (mem − h e l p e r ( curry eq ? o b j ) id ) #f l s t ) ) ( define (memv o b j l s t ) ( f o l d (mem − h e l p e r ( curry eqv ? o b j ) id ) #f l s t ) ) ( define ( member o b j l s t ) ( f o l d (mem − h e l p e r ( curry e q u a l ? o b j ) id ) #f l s t ) ) ( define ( a s s q o b j a l i s t ) ( f o l d (mem − h e l p e r ( curry eq ? o b j ) c a r ) #f a l i s t ) ) ( define ( a s s v o b j a l i s t ) ( f o l d (mem − h e l p e r ( curry eqv ? o b j ) c a r ) #f a l i s t ) ) ( define ( a s s o c o b j a l i s t ) ( f o l d (mem − h e l p e r ( curry e q u a l ? o b j ) c a r ) #f a l i s t ) ) The helper function is parameterized by the predicate to use and the operation to apply to the result if found. (2 . (2 . NIL)))). TOWARDS A STANDARD LIBRARY there’s a bit of subtlety at work. returning a new list with the transformed values: ( define (map f u n c l s t ) ( f o l d r ( lambda ( x y ) ( cons ( func x ) y ) ) ’() . (4 . There’s a whole family of member and assoc functions. let’s define the functions map and filter. (3 . The particular lambda expression is fairly complicated though. If you want to reverse this. (1 .

If it’s true. This eliminates all the elements that don’t satisfy the predicate. dropping all others: ( define ( f i l t e r pred l s t ) ( f o l d r ( lambda ( x y ) ( i f ( pred x ) ( cons x y ) y) ) ’() lst )) This works by testing the current value against the predicate.97 lst )) Remember that foldr’s function takes its arguments in the opposite order as fold. represented by the right-hand argument. consing up a new list that includes only the ones that do. drop the cons and just return the rest of the list.e. but also applies the function to its left-side argument. If it’s false.1: Standard library (stdlib. It’s essentially replacing every infix cons constructor with one that conses. i. then conses it with the rest of the mapped list. replacing cons with cons. filter keeps only the elements of a list that satisfy a predicate. don’t do anything. map’s lambda applies the function to the current value.scm) ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( define ( caar pair ) ( car ( car pair ) ) ) ( cadr p a i r ) ( car ( cdr p a i r ) ) ) ( cdar p a i r ) ( cdr ( car p a i r ) ) ) ( cddr p a i r ) ( cdr ( cdr p a i r ) ) ) ( caaar pair ) ( car ( car ( car pair ) ) ) ) ( caadr p a i r ) ( car ( car ( cdr p a i r ) ) ) ) ( cadar p a i r ) ( car ( cdr ( car p a i r ) ) ) ) ( caddr p a i r ) ( car ( cdr ( cdr p a i r ) ) ) ) ( cdaar p a i r ) ( cdr ( car ( car p a i r ) ) ) ) ( cdadr p a i r ) ( cdr ( car ( cdr p a i r ) ) ) ) ( cddar p a i r ) ( cdr ( cdr ( car p a i r ) ) ) ) ( cdddr p a i r ) ( c d r ( c d r ( c d r p a i r ) ) ) ) ( caaaar pair ) ( car ( car ( car ( car pair ) ) ) ) ) ( caaadr p a i r ) ( car ( car ( car ( cdr p a i r ) ) ) ) ) ( caadar p a i r ) ( car ( car ( cdr ( car p a i r ) ) ) ) ) ( caaddr p a i r ) ( car ( car ( cdr ( cdr p a i r ) ) ) ) ) ( cadaar p a i r ) ( car ( cdr ( car ( car p a i r ) ) ) ) ) ( cadadr p a i r ) ( car ( cdr ( car ( cdr p a i r ) ) ) ) ) ( caddar p a i r ) ( car ( cdr ( cdr ( car p a i r ) ) ) ) ) ( cadddr p a i r ) ( c a r ( c d r ( c d r ( c d r p a i r ) ) ) ) ) ( cdaaar p a i r ) ( cdr ( car ( car ( car p a i r ) ) ) ) ) ( cdaadr p a i r ) ( cdr ( car ( car ( cdr p a i r ) ) ) ) ) ( cdadar p a i r ) ( cdr ( car ( cdr ( car p a i r ) ) ) ) ) ( cdaddr p a i r ) ( c d r ( c a r ( c d r ( c d r p a i r ) ) ) ) ) ( cddaar p a i r ) ( cdr ( cdr ( car ( car p a i r ) ) ) ) ) ( cddadr p a i r ) ( c d r ( c d r ( c a r ( c d r p a i r ) ) ) ) ) ( cdddar p a i r ) ( c d r ( c d r ( c d r ( c a r p a i r ) ) ) ) ) ( cddddr p a i r ) ( c d r ( c d r ( c d r ( c d r p a i r ) ) ) ) ) ( not x ) ( null ? obj ) ( id o b j ) ( f l i p func ) ( curry f u n c a r g 1 ) ( i f x #f #t ) ) ( i f ( eqv ? o b j ’ ( ) ) #t #f ) ) obj ) ( lambda ( a r g 1 a r g 2 ) ( f u n c a r g 2 a r g 1 ) ) ) ( lambda ( a r g ) ( f u n c a r g 1 a r g ) ) ) 5 10 15 20 25 30 . The complete standard library is therefore: Listing 10. with the current value on the left.

TOWARDS A STANDARD LIBRARY ( lambda ( a r g ) ( f ( g a r g ) ) ) ) ( d e f i n e ( compose f g ) 40 ( d e f i n e ( f o l d l f u n c accum l s t ) ( i f ( null ? l s t ) accum ( f o l d l f u n c ( f u n c accum ( c a r l s t ) ) ( cdr lst )))) 45 ( d e f i n e ( f o l d r f u n c accum l s t ) ( i f ( null ? l s t ) accum ( f u n c ( c a r l s t ) ( f o l d r f u n c accum ( c d r lst ))))) 50 ( d e f i n e ( u n f o l d f u n c i n i t pred ) ( i f ( pred i n i t ) ( cons i n i t ’ ( ) ) ( c o n s i n i t ( u n f o l d f u n c ( f u n c i n i t ) pred ) ) ) ) ( define f o l d f o l d l ) ( define reduce f o l d ) 55 60 65 70 ( d e f i n e zero ? ( curry = 0 ) ) ( define p o s i t i v e ? ( curry < 0 ) ) ( define n e g a t i v e ? ( curry > 0 ) ) ( d e f i n e ( odd ? num) (= (mod num 2 ) 1 ) ) ( d e f i n e ( even ? num) (= (mod num 2 ) 0 ) ) ( d e f i n e ( max x . l s t ) ( f o l d r ( lambda ( x y ) ( c o n s ( f u n c x ) y ) ) ( f o l d r ( lambda ( x y ) ( i f ’() 75 ( pred x ) ( c o n s x y ) 80 ( fold + 0 lst ) ) ( fold ∗ 1 lst ) ) ( f o l d && #t l s t ) ) ( f o l d | | #f l s t ) ) ( a p p l y or (map pred l s t ) ) ) ( a p p l y and (map pred l s t ) ) ) We can use the standard library by starting up our Lisp interpreter and typing (load "stdlib. num− l i s t ) ( f o l d ( lambda ( y z ) ( i f (< y z ) y z ) ) x num− list )) ( define ( l i s t . l s t ) ( and .98 35 CHAPTER 10. o b j s ) objs ) ( d e f i n e ( length l s t ) ( f o l d ( lambda ( x y ) (+ x 1 ) ) 0 l s t ) ) ( d e f i n e ( append l s t . l s t ) ( product .scm"): user>> . l s t ) . l s t ) ( any ? pred . / l i s p Lisp>>> ( l o a d " stdlib . . l s t s ) ( f o l d r ( f l i p ( curry f o l d r c o n s ) ) l s t l s t s ) ) ( d e f i n e ( reverse l s t ) ( f o l d ( f l i p cons ) ’ ( ) l s t ) ) ( d e f i n e (mem − h e l p e r pred op ) ( lambda ( a c c next ) ( i f ( and ( not a c c ) ( pred ( op next ) ) ) next a c c ) ) ) ( d e f i n e (memq o b j l s t ) ( f o l d (mem − h e l p e r ( curry eq ? o b j ) id ) #f lst )) ( d e f i n e (memv o b j l s t ) ( f o l d (mem − h e l p e r ( curry eqv ? o b j ) id ) #f lst )) ( d e f i n e ( member o b j l s t ) ( f o l d (mem − h e l p e r ( curry e q u a l ? o b j ) id ) #f lst )) ( define ( assq obj a l i s t ) ( f o l d (mem − h e l p e r ( curry eq ? o b j ) c a r ) #f alist )) ( define ( assv obj a l i s t ) ( f o l d (mem − h e l p e r ( curry eqv ? o b j ) c a r ) #f alist )) ( define ( a s s o c obj a l i s t ) ( f o l d (mem − h e l p e r ( curry e q u a l ? o b j ) c a r ) # f alist )) ( d e f i n e (map f u n c l s t ) lst )) ( d e f i n e ( f i l t e r pred l s t ) y) ) ’() lst ) ) ( define ( define ( define ( define ( define ( define ( sum . scm " ) ( lambda ( " pred " . l s t ) ( or . l s t ) ( e v e r y ? pred . . ) . num− l i s t ) ( f o l d ( lambda ( y z ) ( i f (> y z ) y z ) ) x num− list )) ( d e f i n e ( min x .

the key to successful fold-programming is thinking only in terms of what happens on each iteration. list-ref. Remember. and recursive problems are best solved by working one step at a time. including list-tail. Try implementing them as folds. Fold captures the pattern of recursion down a list. . append. and various string-manipulation functions.99 Lisp>>> (3 4 5 Lisp>>> (2 4) Lisp>>> (map ( c u r r y + 2 ) ’ ( 1 2 3 4 ) ) 6) ( f i l t e r even ? ’ ( 1 2 3 4 ) ) quit There are many other useful functions that could go into the standard library.

TOWARDS A STANDARD LIBRARY .100 CHAPTER 10.

symbols. or in batch mode. strings. list manipulation. Section 4. you can format the output of UNIX commands as parenthesized Lisp lists. They’re a very convenient feature for adding new language features. You can write libraries of Scheme functions and either include them in programs or load them into the interactive interpreter. instead of returning a result. a monadic web framework 101 • live version • discussion • edit • comment • report an error . Continuations are a way of capturing ”the rest of the computation”. If you’re just interested in learning more Haskell. Using them. there are a large number of libraries that may help: • For webapps: WASH. With a little text processing via awk or sed.3 of R5RS defines the macro system’s syntax and semantics. This parameter gets threaded through all recursive calls to eval. and perhaps executing it more than once. with a REPL. you’d want to intersperse a function between readExpr and eval that takes a form and a macro environment. but only is only manipulated when evaluating a call to call-with-current-continuation. lexical scoping. saving it. The easiest way to implement continuations is to transform the program into continuation-passing style. and assignment. and there is a whole collection of papers on implementation. running script files. Basically. so that eval takes an additional continuation argument and calls it.Conclusion & Further Resources You now have a working Scheme interpreter that implements a large chunk of the standard. rewriting variables as necessarily. including functions. There’re still a number of features you could add to this interpreter. and use this interpreter for shell scripting. and several standard parts of Scheme (such as let-bindings and additional control flow features) are defined in terms of them. lambdas. you can implement just about every control flow feature in every major programming language. looks for transformer keywords. and then transforms them according to the rules of the pattern language. read them into a Scheme program. Hygienic macros let you perform transformations on the source code before it’s executed. Dynamic-wind could be implemented by keeping a stack of functions to execute when leaving the current continuation and storing (inside the continuation data type) a stack of functions to execute when resuming the continuation. integers. You can use it interactively.

while Fudgets includes a lot of new research about how to represent GUIs in functional programming languages • For concurrency: Software Transactional Memory. a library that wraps SQL as a set of Haskell functions.102 CONCLUSION • For databases: HaskellDB. giving you all the type-safety of the language when querying the database • For GUI programming: Fudgets and wxHaskell. Happy hacking! . wxHaskell is more of a conventional MVC GUI library. described in the paper Composable Memory Transactions • For networking: GHC’s Networking libraries This should give you a starting point for further investigations into the language.

E r r o r Data . IORef Text . P a r s e c hiding ( s p a c e s ) IO hiding ( try ) 5 10 main : : IO ( ) main = do a r g s <− getArgs i f nu ll a r g s then runRepl e l s e runOne $ a r g s symbol : : P a r s e r Char symbol = oneOf "!$ %&|*+ -/: <=? > @^_ ~# " 15 20 readOrThrow : : P a r s e r a −> String −> ThrowsError a readOrThrow p a r s e r i n p u t = case p a r s e p a r s e r " lisp " i n p u t of Left e r r −> t h r o w E r r o r $ P a r s e r e r r Right v a l −> return v a l readExpr = readOrThrow parseExpr r e a d E x p r L i s t = readOrThrow ( endBy parseExpr s p a c e s ) spaces : : Parser () s p a c e s = skipMany1 s p a c e data L i s p V a l = | | | | | | Atom String Li st [ L i s p V a l ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool Port Handle 25 30 103 . Environment C o n t r o l .Appendix A Complete Parser module import import import import import import Main where Monad System . Monad . P a r s e r C o m b i n a t o r s .

body : : [ L i s p V a l ] . v a r a r g : : ( Maybe String ) . x ] parseExpr : : P a r s e r L i s p V a l parseExpr = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> parseQuoted <|> do c h a r ’ ( ’ x <− ( try p a r s e L i s t ) <|> p a r s e D o t t e d L i s t char ’ ) ’ return x 65 70 75 80 . c l o s u r e : : Env } 35 40 p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’"’ x <− many ( noneOf " ###BOT_TEXT###quot; " ) c h a r ’"’ return $ String x parseAtom : : P a r s e r L i s p V a l parseAtom = do f i r s t <− l e t t e r <|> symbol r e s t <− many ( l e t t e r <|> d i g i t <|> symbol ) l e t atom = [ f i r s t ] ++ r e s t return $ case atom of "#t" −> Bool True "#f" −> Bool False otherwise −> Atom atom parseNumber : : P a r s e r L i s p V a l parseNumber = liftM ( Number .104 APPENDIX A. COMPLETE PARSER | P r i m i t i v e F u n c ( [ L i s p V a l ] −> ThrowsError L i s p V a l ) | IOFunc ( [ L i s p V a l ] −> IOThrowsError L i s p V a l ) | Func { params : : [ String ] . ’ >> s p a c e s >> parseExpr return $ D o t t e d L i s t head t a i l parseQuoted : : P a r s e r L i s p V a l parseQuoted = do char ’ \ ’ ’ x <− parseExpr return $ Li s t [ Atom " quote " . read ) $ many1 d i g i t p a r s e L i s t : : Parser LispVal p a r s e L i s t = liftM L ist $ sepBy parseExpr s p a c e s 45 50 55 60 parseDottedList : : Parser LispVal p a r s e D o t t e d L i s t = do head <− endBy parseExpr s p a c e s t a i l <− c h a r ’ .

v a r a r g = v a r a r g s . Atom var .) " u n w o r d s L i s t : : [ L i s p V a l ] −> String u n w o r d s L i s t = unwords .105 showVal : : L i s p V a l −> String showVal ( String c o n t e n t s ) = " ###BOT_TEXT###quot; " ++ c o n t e n t s ++ " ###BOT_TEXT###quot; " showVal (Atom name ) = name showVal ( Number c o n t e n t s ) = show c o n t e n t s showVal ( Bool True ) = "#t" showVal ( Bool False ) = "#f" showVal ( Li st c o n t e n t s ) = "(" ++ u n w o r d s L i s t c o n t e n t s ++ ")" showVal ( D o t t e d L i s t head t a i l ) = "(" ++ u n w o r d s L i s t head ++ " . Atom var .. conseq . v a l ] ) = return v a l env ( Li st [ Atom " if " . a l t ] ) = do r e s u l t <− e v a l env pred case r e s u l t of Bool False −> e v a l env a l t otherwise −> e v a l env c o n s e q e v a l env ( Li st [ Atom " set !" . c l o s u r e = env } ) = "( lambda (" ++ unwords (map show a r g s ) ++ ( case v a r a r g s of Nothing −> "" Just a r g −> " . pred . form ] ) = e v a l env form >>= d e f i n e V a r env var e v a l env ( Li st (Atom " define " : L is t (Atom var : params ) : body ) ) = makeNormalFunc env params body >>= d e f i n e V a r env var e v a l env ( Li st (Atom " define " : D o t t e d L i s t (Atom var : params ) v a r a r g s : body ) ) = makeVarargs v a r a r g s env params body >>= d e f i n e V a r env var e v a l env ( Li st (Atom " lambda " : L is t params : body ) ) = makeNormalFunc env params body e v a l env ( Li st (Atom " lambda " : D o t t e d L i s t params v a r a r g s : body ) ) = makeVarargs v a r a r g s env params body eval eval eval eval eval eval eval 85 90 95 100 105 110 115 120 125 . body = body .. form ] ) = e v a l env form >>= s e t V a r env var e v a l env ( Li st [ Atom " define " . map showVal instance Show L i s p V a l where show = showVal : : Env −> L i s p V a l −> IOThrowsError L i s p V a l env val@ ( String ) = return v a l env val@ ( Number ) = return v a l env val@ ( Bool ) = return v a l env (Atom id ) = getVar env id env ( Li st [ Atom " quote " . " ++ a r g ) ++ ") . " ++ showVal t a i l ++ ")" showVal ( Port ) = " <IO port >" showVal ( IOFunc ) = " <IO primitive >" showVal ( P r i m i t i v e F u n c ) = " < primitive >" showVal ( Func { params = a r g s .

( " remainder " . ( " string ?" . numericBinop (+) ) . numBoolBinop (/=) ) . c a r ) . mapM ( e v a l env ) e v a l env ( Li s t ( f u n c t i o n : a r g s ) ) = do f u n c <− e v a l env f u n c t i o n a r g V a l s <− mapM ( e v a l env ) a r g s apply func argVals e v a l env badForm = t h r o w E r r o r $ BadSpecialForm " Unrecognized special form " badForm a p p l y : : L i s p V a l −> [ L i s p V a l ] −> IOThrowsError L i s p V a l apply ( PrimitiveFunc func ) args = l i f t T h r o w s $ func args a p p l y ( IOFunc f u n c ) a r g s = f u n c a r g s a p p l y ( Func params v a r a r g s body c l o s u r e ) a r g s = i f num params /= num a r g s && v a r a r g s == Nothing then t h r o w E r r o r $ NumArgs (num params ) a r g s e l s e ( l i f t I O $ bindVars c l o s u r e $ zip params a r g s ) >>= bindVarArgs v a r a r g s >>= evalBody where r e m a i n i n g A r g s = drop ( length params ) a r g s num = toInteger . b o o l B o o l B i n o p (&&) ) . COMPLETE PARSER 130 135 e v a l env ( Li s t (Atom " lambda " : varargs@ (Atom ) : body ) ) = makeVarargs v a r a r g s env [ ] body e v a l env ( Li s t [ Atom " load " . L ist $ r e m a i n i n g A r g s ) ] Nothing −> return env p r i m i t i v e s : : [ ( String . c o n s ) . ( " >=" . s t r B o o l B i n o p (<=) ) . ( " string <=? " . length evalBody env = liftM l a s t $ mapM ( e v a l env ) body bindVarArgs a r g env = case a r g of Just argName −> l i f t I O $ bindVars env [ ( argName . ( " >" . numericBinop div ) . ( " cdr " . ( " car " .106 APPENDIX A. numericBinop mod) . ( " <" . ( "=" . String f i l e n a m e ] ) = l o a d f i l e n a m e >>= liftM l a s t . b o o l B o o l B i n o p ( | | ) ) . numBoolBinop ( > ) ) . ( " -" . s t r B o o l B i n o p (>=) ) . s t r B o o l B i n o p ( > ) ) . ( " string >=? " . ( " <=" . numBoolBinop ( < ) ) . ( "*" . 140 145 150 155 160 165 170 . numBoolBinop (>=) ) . numericBinop ( ∗ ) ) . [ L i s p V a l ] −> ThrowsError L i s p V a l ) ] p r i m i t i v e s = [ ( "+" . c d r ) . ( " || " . ( " string =? " . ( " quotient " . numBoolBinop (==) ) . numBoolBinop (<=) ) . ( "/" . ( " mod " . numericBinop ( − ) ) . numericBinop quot ) . ( " && " . ( " cons " . s t r B o o l B i n o p (==) ) . ( " /= " . numericBinop rem ) .

( " eqv ?" . eqv ) . Number .107 ( " eq ?" . ( " equal ?" . f o l d l 1 op b ool Bi no p : : ( L i s p V a l −> ThrowsError a ) −> ( a −> a −> Bool ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l b ool Bi no p unpacker op a r g s = i f length a r g s /= 2 then t h r o w E r r o r $ NumArgs 2 a r g s e l s e do l e f t <− unpacker $ a r g s !! 0 r i g h t <− unpacker $ a r g s !! 1 return $ Bool $ l e f t ‘ op ‘ right numBoolBinop = b oo l B i no p unpackNum s t r B o o l B i n o p = b oo l B i no p unpackStr b o o l B o o l B i n o p = b oo l B i no p unpackBool unpackNum : : L i s p V a l −> ThrowsError Integer unpackNum ( Number n ) = return n unpackNum ( String n ) = l e t p a r s e d = reads n in i f null p a r s e d then t h r o w E r r o r $ TypeMismatch " number " $ String n e l s e return $ f s t $ p a r s e d ! ! 0 unpackNum ( Li s t [ n ] ) = unpackNum n unpackNum notNum = t h r o w E r r o r $ TypeMismatch " number " notNum unpackStr : : L i s p V a l −> ThrowsError String unpackStr ( String s ) = return s unpackStr ( Number s ) = return $ show s unpackStr ( Bool s ) = return $ show s unpackStr n o t S t r i n g = t h r o w E r r o r $ TypeMismatch " string " notString unpackBool : : L i s p V a l −> ThrowsError Bool unpackBool ( Bool b ) = return b unpackBool notBool = t h r o w E r r o r $ TypeMismatch " boolean " notBool c a r : : [ L i s p V a l ] −> ThrowsError L i s p V a l c a r [ Li st ( x : xs ) ] = return x 175 180 185 190 195 200 205 210 . eqv ) . e q u a l ) ] numericBinop : : ( Integer −> Integer −> Integer ) −> [ L i s p V a l ] −> ThrowsError L i s p V a l numericBinop op s i n g l e V a l @ [ ] = t h r o w E r r o r $ NumArgs 2 singleVal numericBinop op params = mapM unpackNum params >>= return .

] = return $ Bool False eqv badArgLi s t = t h r o w E r r o r $ NumArgs 2 badArgList data Unpacker = f o r a l l a . ( L ist a r g 2 ) ] = return $ Bool $ ( length a r g 1 == length a r g 2 ) && ( and $ map eqvPair $ zip arg1 arg2 ) 240 where e q v P a i r ( x1 . Li st $ ys ++ [ y ] ] eqv [ ( Li s t a r g 1 ) . ( D o t t e d L i s t ys y ) ] = eqv [ L ist $ xs ++ [ x ] . x2 ] = return $ D o t t e d L i s t [ x1 ] x2 badArgLi s t = t h r o w E r r o r $ NumArgs 2 badArgList 220 225 230 cons cons cons cons ) cons cons 235 eqv : : [ L i s p V a l ] −> ThrowsError L i s p V a l eqv [ ( Bool a r g 1 ) . D o t t e d L i s t xs x l a s t ] = return $ D o t t e d L i s t ( [ x ] ++ xs xlast [ x1 . x2 ) = case eqv [ x1 . ( Bool a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 eqv [ ( Number a r g 1 ) .108 APPENDIX A. (Atom a r g 2 ) ] = return $ Bool $ a r g 1 == a r g 2 eqv [ ( D o t t e d L i s t xs x ) . x2 ] of Left e r r −> False Right ( Bool v a l ) −> v a l eqv [ . Li s t [ ] ] = return $ L ist [ x1 ] [ x . COMPLETE PARSER 215 c a r [ D o t t e d L i s t ( x : xs ) ] = return x c a r [ badArg ] = t h r o w E r r o r $ TypeMismatch " pair " badArg c a r badArgLi s t = t h r o w E r r o r $ NumArgs 1 badArgList cdr cdr cdr cdr cdr cdr : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ Li st ( x : xs ) ] = return $ L is t xs [ DottedList ( : xs ) x ] = return $ D o t t e d L i s t xs x [ D o t t e d L i s t [ xs ] x ] = return x [ badArg ] = t h r o w E r r o r $ TypeMismatch " pair " badArg badArgLi s t = t h r o w E r r o r $ NumArgs 1 badArgList : : [ L i s p V a l ] −> ThrowsError L i s p V a l [ x1 . ( String a r g 2 ) ] = return $ Bool $ a r g 1 == arg2 eqv [ ( Atom a r g 1 ) . Eq a = > AnyUnpacker ( L i s p V a l −> ThrowsError a ) unpackEquals : : L i s p V a l −> L i s p V a l −> Unpacker −> ThrowsError Bool unpackEquals a r g 1 a r g 2 ( AnyUnpacker unpacker ) = do unpacked1 <− unpacker a r g 1 unpacked2 <− unpacker a r g 2 245 250 . Li st xs ] = return $ L ist $ [ x ] ++ xs [ x . ( Number a r g 2 ) ] = return $ Bool $ a r g 1 == arg2 eqv [ ( String a r g 1 ) .

a r g 2 ] = do p r i m i t i v e E q u a l s <− liftM or $ mapM ( unpackEquals a r g 1 a r g 2 ) [ AnyUnpacker unpackNum . show ) 280 285 290 .109 return $ unpacked1 == unpacked2 ‘ c a t c h E r r o r ‘ ( const $ return False ) 255 260 e q u a l : : [ L i s p V a l ] −> ThrowsError L i s p V a l e q u a l [ arg1 . found " ++ show found showError ( P a r s e r p a r s e E r r ) = " Parse error at " ++ show parseErr instance Show L i s p E r r o r where show = showError instance E r r o r L i s p E r r o r where noMsg = D e f a u l t " An error has occurred " strMsg = D e f a u l t type ThrowsError = Either L i s p E r r o r t r a p E r r o r a c t i o n = c a t c h E r r o r a c t i o n ( return . AnyUnpacker unpackBool ] eqvEqual s <− eqv [ arg1 . AnyUnpacker unpackStr . a r g 2 ] return $ Bool $ ( p r i m i t i v e E q u a l s | | l e t ( Bool x ) = eqvEqual s in x ) e q u a l badArgLi s t = t h r o w E r r o r $ NumArgs 2 badArgList data L i s p E r r o r = | | | | | | NumArgs Integer [ L i s p V a l ] TypeMismatch String L i s p V a l Parser ParseError BadSpecialForm String L i s p V a l NotFunction String String UnboundVar String String D e f a u l t String 265 270 275 showError : : L i s p E r r o r −> String showError ( UnboundVar message varname ) = message ++ ": " ++ varname showError ( BadSpecialForm message form ) = message ++ ": " ++ show form showError ( NotFunction message f u n c ) = message ++ ": " ++ show func showError ( NumArgs e x p e c t e d found ) = " Expected " ++ show expected ++ " args : found values " ++ u n w o r d s L i s t found showError ( TypeMismatch e x p e c t e d found ) = " Invalid type : expected " ++ e x p e c t e d ++ " .

f u n c ) = ( var . e v a l A n d P r i n t 315 320 (== " quit " ) ( readPrompt type Env = IORef [ ( String . String ( a r g s ! ! 0 ) ] ) ) >>= hPutStrLn stderr runRepl : : IO ( ) runRepl = p r i m i t i v e B i n d i n g s >>= u n t i l " Lisp >>> " ) . c o n s t r u c t o r func ) 325 330 . IORef L i s p V a l ) ] null Env : : IO Env null Env = newIORef [ ] p r i m i t i v e B i n d i n g s : : IO Env p r i m i t i v e B i n d i n g s = nullE nv >>= ( f l i p bindVars $ map ( makeFunc IOFunc ) i o P r i m i t i v e s ++ map ( makeFunc PrimitiveFunc ) primitives ) where makeFunc c o n s t r u c t o r ( var .110 APPENDIX A. COMPLETE PARSER e x t r a c t V a l u e : : ThrowsError a −> a e x t r a c t V a l u e ( Right v a l ) = v a l f l u s h S t r : : String −> IO ( ) f l u s h S t r s t r = putStr s t r >> hFlush stdout readPrompt : : String −> IO String readPrompt prompt = f l u s h S t r prompt >> getLine 300 295 e v a l A n d P r i n t : : Env −> String −> IO ( ) e v a l A n d P r i n t env expr = e v a l S t r i n g env expr >>= putStrLn e v a l S t r i n g : : Env −> String −> IO String e v a l S t r i n g env expr = runIOThrows $ liftM show $ ( l i f t T h r o w s $ readExpr expr ) >>= e v a l env 305 310 until : : Monad m = > ( a −> Bool ) −> m a −> ( a −> m ( ) ) −> m ( ) u n t i l pred prompt a c t i o n = do r e s u l t <− prompt i f pred r e s u l t then return ( ) e l s e a c t i o n r e s u l t >> u n t i l pred prompt a c t i o n runOne : : [ String ] −> IO ( ) runOne a r g s = do env <− p r i m i t i v e B i n d i n g s >>= f l i p bindVars [ ( " args " . L ist $ map String $ drop 1 a r g s ) ] ( runIOThrows $ liftM show $ e v a l env ( L is t [ Atom " load " .

extractValue 340 335 isBound : : Env −> String −> IO Bool isBound envRef var = readIORef envRef >>= return . v a l u e ) = do r e f <− newIORef v a l u e return ( var . lookup var getVar : : Env −> String −> IOThrowsError L i s p V a l getVar envRef var = do env <− l i f t I O $ readIORef envRef maybe ( t h r o w E r r o r $ UnboundVar " Getting an unbound variable " var ) ( l i f t I O . readIORef ) ( lookup var env ) s e t V a r : : Env −> String −> L i s p V a l −> IOThrowsError L i s p V a l s e t V a r envRef var v a l u e = do env <− l i f t I O $ readIORef envRef maybe ( t h r o w E r r o r $ UnboundVar " Setting an unbound variable " var ) ( l i f t I O .111 type IOThrowsError = ErrorT L i s p E r r o r IO l i f t T h r o w s : : ThrowsError a −> IOThrowsError a l i f t T h r o w s ( Left e r r ) = t h r o w E r r o r e r r l i f t T h r o w s ( Right v a l ) = return v a l runIOThrows : : IOThrowsError String −> IO String runIOThrows a c t i o n = runErrorT ( t r a p E r r o r a c t i o n ) >>= return . r e f ) 345 350 355 360 365 370 . maybe False ( const True ) . L i s p V a l ) ] −> IO Env bindVars envRef b i n d i n g s = readIORef envRef >>= extendEnv b i n d i n g s >>= newIORef where extendEnv b i n d i n g s env = liftM (++ env ) (mapM addBinding b i n d i n g s ) addBinding ( var . ( f l i p writeIORef value ) ) ( lookup var env ) return v a l u e d e f i n e V a r : : Env −> String −> L i s p V a l −> IOThrowsError L i s p V a l d e f i n e V a r envRef var v a l u e = do a l r e a d y D e f i n e d <− l i f t I O $ isBound envRef var i f alreadyDefined then s e t V a r envRef var v a l u e >> return v a l u e e l s e l i f t I O $ do v a l u e R e f <− newIORef v a l u e env <− readIORef envRef w r i t e I O R e f envRef ( ( var . v a l u e R e f ) : env ) return v a l u e bindVars : : Env −> [ ( String .

112

APPENDIX A. COMPLETE PARSER

375

makeFunc v a r a r g s env params body = return $ Func (map showVal params ) v a r a r g s body env makeNormalFunc = makeFunc Nothing makeVarargs = makeFunc . Just . showVal i o P r i m i t i v e s : : [ ( String , [ L i s p V a l ] −> IOThrowsError L i s p V a l ) ] i o P r i m i t i v e s = [ ( " apply " , applyP roc ) , ( " open - input - file " , makePort ReadMode ) , ( " open - output - file " , makePort WriteMode ) , ( " close - input - port " , c l o s e P o r t ) , ( " close - output - port " , c l o s e P o r t ) , ( " read " , r e a d P r o c ) , ( " write " , w r i t e P r o c ) , ( " read - contents " , r e a d C o n t e n t s ) , ( " read - all " , r e a d A l l ) ] appl yPro c : : [ L i s p V a l ] −> IOThrowsError L i s p V a l appl yPro c [ func , L is t a r g s ] = a pply f u n c a r g s appl yPro c ( f u n c : a r g s ) = a pply f u n c a r g s makePort : : IOMode −> [ L i s p V a l ] −> IOThrowsError L i s p V a l makePort mode [ String f i l e n a m e ] = liftM Port $ l i f t I O $ openFile f i l e n a m e mode

380

385

390

395

c l o s e P o r t : : [ L i s p V a l ] −> IOThrowsError L i s p V a l c l o s e P o r t [ Port p o r t ] = l i f t I O $ hClose p o r t >> ( return $ Bool True ) closePort = return $ Bool False
400

r e a d P r o c : : [ L i s p V a l ] −> IOThrowsError L i s p V a l r e a d P r o c [ ] = r e a d P r o c [ Port stdin ] r e a d P r o c [ Port p o r t ] = ( l i f t I O $ hGetLine p o r t ) >>= l i f t T h r o w s . readExpr writeProc writeProc writeProc return : : [ L i s p V a l ] −> IOThrowsError L i s p V a l [ o b j ] = w r i t e P r o c [ obj , Port stdout ] [ obj , Port p o r t ] = l i f t I O $ hPrint p o r t o b j >> ( $ Bool True )

405

r e a d C o n t e n t s : : [ L i s p V a l ] −> IOThrowsError L i s p V a l r e a d C o n t e n t s [ String f i l e n a m e ] = liftM String $ l i f t I O $ readFile f i l e n a m e
410

l o a d : : String −> IOThrowsError [ L i s p V a l ] l o a d f i l e n a m e = ( l i f t I O $ readFile f i l e n a m e ) >>= l i f t T h r o w s . readExprList r e a d A l l : : [ L i s p V a l ] −> IOThrowsError L i s p V a l r e a d A l l [ String f i l e n a m e ] = liftM L ist $ l o a d f i l e n a m e

415

Appendix B

Answers to Exercises
Section 2.3
Exercise 1
Part 1
parseNumber : : P a r s e r L i s p V a l parseNumber = do x <− many1 d i g i t ( return . Number . read ) x

• live version • discussion • edit • comment • report an error section] section] section] section]

[edit [edit Part 2 [edit In order to anwer this question, you need to do a bit of detective work! It [edit is helpful to read up on do notation. Using the information there, we can mechanically transform the above answer into the following.
parseNumber = many1 d i g i t >>= \ x −> ( return . Number . read ) x

This can be cleaned up into the following:
parseNumber = many1 d i g i t >>= return . Number . read

Exercise 2
We need to create a new parser action that accepts a backslash followed by either another backslash or a doublequote. This action needs to return only the second character.
e s c a p e d C h a r s : : P a r s e r String e s c a p e d C h a r s = do c h a r ’ \ \ ’ −− a b a c k s l a s h x <− oneOf " \###BOT_TEXT###quot; " −− e i t h e r b a c k s l a s h o r doublequote return [ x ] −− make t h i s c h a r a c t e r i n t o a string

[edit section]

113

114

APPENDIX B. ANSWERS TO EXERCISES

Once that is done, we need to make some changes to parseString.
p a r s e S t r i n g : : Parser LispVal p a r s e S t r i n g = do c h a r ’ " ’ x <- many $ many1 ( noneOf " \ " \ " ) <|> escapedChars char ’ "’ return $ String ( concat x)

[edit section]

Exercise 3
e s c a p e d C h a r s : : P a r s e r String e s c a p e d C h a r s = do c h a r ’ \ \ ’ x <− oneOf " \###BOT_TEXT###quot; ntr " case x of ’ \ \ ’ −> do return [ x ] ’ " ’ -> do return [x] ’t ’ -> do return " \ t " ’n ’ -> do return " \ n " ’r ’ -> do return " \ r "

[edit section]

Exercise 4
First, it is necessary to change the definition of symbol.
symbol : : P a r s e r Char symbol = oneOf "!$ %&|*+ -/: < = >?@^_~"

This means that it is no longer possible to begin an atom with the hash character. This necessitates a different way of parsing #t and #f.
parseBool : : Parser LispVal p a r s e B o o l = do s t r i n g "#" x <− oneOf " tf " return $ case x of ’ t ’ −> Bool True ’ f ’ −> Bool False

This in turn requires us to make changes to parseExpr.
parseExpr : : P a r s e r L i s p V a l parseExpr = parseAtom <|> p a r s e S t r i n g <|> parseNumber <|> p a r s e B o o l

parseNumber need to be changed to the following.
parseNumber : : P a r s e r L i s p V a l parseNumber = do num <− p a r s e D i g i t a l 1 <|> p a r s e D i g i t a l 2 <|> parseHex <|> p a r s e O c t <|> p a r s e B i n return $ num

SECTION 2.3 And the following new functions need to be added.
p a r s e D i g i t a l 1 : : Parser LispVal p a r s e D i g i t a l 1 = do x <− many1 d i g i t ( return . Number . read ) x p a r s e D i g i t a l 2 : : Parser LispVal p a r s e D i g i t a l 2 = do try $ s t r i n g "#d" x <− many1 d i g i t ( return . Number . read ) x parseHex : : P a r s e r L i s p V a l parseHex = do try $ s t r i n g "#x" x <− many1 h e x D i g i t return $ Number ( h e x 2 d i g x ) parseOct : : Parser LispVal p a r s e O c t = do try $ s t r i n g "#o" x <− many1 o c t D i g i t return $ Number ( o c t 2 d i g x ) parseBin : : Parser LispVal p a r s e B i n = do try $ s t r i n g "#b" x <− many1 ( oneOf " 10 " ) return $ Number ( b i n 2 d i g x )

115

o c t 2 d i g x = f s t $ readOct x ! ! 0 h e x 2 d i g x = f s t $ readHex x ! ! 0 bin2dig = bin2dig ’ 0 b i n 2 d i g ’ d i g i n t "" = d i g i n t b i n 2 d i g ’ d i g i n t ( x : xs ) = l e t o l d = 2 ∗ d i g i n t + ( i f x == ’ 0 ’ then 0 e l s e 1 ) in b i n 2 d i g ’ o l d xs

Exercise 5
data L i s p V a l = | | | | | | Atom String Li st [ L i s p V a l ] DottedList [ LispVal ] LispVal Number Integer String String Bool Bool C h a r a c t e r Char

[edit section]

parseChar : : P a r s e r L i s p V a l parseChar = do try $ s t r i n g " #\ " x <− parseCharName <|> anyChar return $ C h a r a c t e r x

parseExpr : : P a r s e r L i s p V a l parseExpr = parseAtom <|> p a r s e S t r i n g <|> try parseNumber −− we need t h e ’ t r y ’ b e c a u s e <|> try p a r s e B o o l −− t h e s e can a l l s t a r t with t h e hash char <|> try parseChar parseCharName = do x <− try ( s t r i n g " space " <|> s t r i n g " newline " ) case x of " space " −> do return ’ ’ " newline " −> do return ’ \ n ’ .116 APPENDIX B. ANSWERS TO EXERCISES Note that this does not actually conform to the standard. space and newline must be entirely lowercase. the standard states that they should be case insensitive. as it stands.

Printer-friendly PDF: http://en.pdf A 3. It was imported into the Wikibooks project on 2006-07-08 by Kowey and developed on the project by the contributors listed in Appendix C. The latest Wikibooks version may be found at http://en.wikibooks.org/ wiki/Write_Yourself_a_Scheme_in_48_Hours.org/wiki/Image:Write_Yourself_ a_Scheme_in_48_Hours_printable_version.wikibooks.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_ 48_Hours/LaTeX 117 . L TEX: http://en.Appendix C Document Information & History History This book was originally written by Jonathan Tang and made available on his website.wikibooks. PDF Information & History A This PDF was complied from L TEX on 2007-07-15. page 118. based on the 2007-07-06 Wikibooks version of the book. PDF: http://en. Document Formats 1.pdf 2.wikibooks. The latest version of the PDF may be found at http://en. For convenience.org/wiki/Image:Write_Yourself_a_Scheme_ in_48_Hours. pdf. this PDF was created for download from the project.org/wiki/Image:Write_Yourself_a_Scheme_in_48_Hours.

org/wiki/Write_Yourself_ a_Scheme_in_48_Hours 5. HTML: http://halogen.note. MediaWiki markup: http://en.edu/~jdtang/scheme_in_48/tutorial/ overview.118 APPENDIX C.amherst. Jonathan Tang Wikibooks Changes • Hagindaz (list of contributions) • Infinoid (list of contributions) • Inhuman14 (list of contributions) • Jguk (list of contributions) • Jkarres (list of contributions) • Kowey (list of contributions) • Sjf (list of contributions) • Whiteknight (list of contributions) • Anonymous Wikibooks contributors .html (original version) Authors Orignal Version 1. DOCUMENT INFORMATION 4.wikibooks.

it can be used for any textual work. textbook. MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document. But this License is not limited to software manuals. It complements the GNU General Public License. Fifth Floor. We recommend this License principally for works whose purpose is instruction or reference. with or without modifying it. We have designed this License in order to use it for manuals for free software. November 2002 Copyright c 2000.Appendix D GNU Free Documentation License Version 1.2002 Free Software Foundation. which means that derivative works of the document must themselves be free in the same sense. this License preserves for the author and publisher a way to get credit for their work. or other functional and useful document “free” in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it. regardless of subject matter or whether it is published as a printed book. Boston. 1. which is a copyleft license designed for free software. APPLICABILITY AND DEFINITIONS 119 . Inc. but changing it is not allowed. This License is a kind of “copyleft”. because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does.2001. 51 Franklin St. either commercially or noncommercially. Secondarily.2. while not being considered responsible for modifications made by others. Preamble The purpose of this License is to make a manual.

If the Document does not identify any Invariant Sections then there are none. if the Document is in part a textbook of mathematics. A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. unlimited in duration. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. You accept the license if you copy. Examples of transparent image formats include PNG. royalty-free license. PostScript or PDF designed for human modification. The “Document”. ethical or political position regarding them. GNU FREE DOCUMENTATION LICENSE This License applies to any manual or other work. represented in a format whose specification is available to the general public. and standard-conforming simple HTML. has been arranged to thwart or discourage subsequent modification by readers is not Transparent. as Front-Cover Texts or Back-Cover Texts. or with modifications and/or translated into another language. The “Invariant Sections” are certain Secondary Sections whose titles are designated. A “Modified Version” of the Document means any work containing the Document or a portion of it. below. to use that work under the conditions stated herein. or absence of markup. A copy that is not “Transparent” is called “Opaque”. SGML or .120 APPENDIX D. in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words. A “Transparent” copy of the Document means a machine-readable copy. (Thus. in any medium. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors. philosophical. that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License.) The relationship could be a matter of historical connection with the subject or with related matters. modify or distribute the work in a way requiring permission under copyright law. either copied verbatim. Examples of suitable formats for Transparent copies include plain ASCII without markup. A copy made in an otherwise Transparent file format whose markup. in the notice that says that the Document is released under this License. Any member of the public is a licensee. XCF and JPG. and is addressed as “you”. or of legal. LaTeX input format. SGML or XML using a publicly available DTD. a Secondary Section may not explain any mathematics. refers to any such manual or work. Such a notice grants a world-wide. as being those of Invariant Sections. An image format is not Transparent if used for any substantial amount of text. The “Cover Texts” are certain short passages of text that are listed. commercial. Texinfo input format. The Document may contain zero Invariant Sections. and a Back-Cover Text may be at most 25 words. that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor.

The “Title Page” means. These Warranty Disclaimers are considered to be included by reference in this License. You may add other material on the covers in addition. plus such following pages as are needed to hold. “Endorsements”. If you distribute a large enough number of copies you must also follow the conditions in section 3. numbering more than 100. the material this License requires to appear in the title page. can be treated as verbatim copying in other respects. A section “Entitled XYZ” means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. preceding the beginning of the body of the text. as long as they preserve the title of the Document and satisfy these conditions. and Back-Cover Texts on the back cover. legibly. or “History”. However. (Here XYZ stands for a specific section name mentioned below. “Dedications”. 3. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. . and the Document’s license notice requires Cover Texts. but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. you must enclose the copies in covers that carry.) To “Preserve the Title” of such a section when you modify the Document means that it remains a section “Entitled XYZ” according to this definition. Both covers must also clearly and legibly identify you as the publisher of these copies. Copying with changes limited to the covers. “Title Page” means the text near the most prominent appearance of the work’s title. under the same conditions stated above. and the machine-generated HTML. and the license notice saying this License applies to the Document are reproduced in all copies. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document. For works in formats which do not have any title page as such. PostScript or PDF produced by some word processors for output purposes only. either commercially or noncommercially. the copyright notices. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. and you may publicly display copies.121 XML for which the DTD and/or processing tools are not generally available. for a printed book. clearly and legibly. and that you add no other conditions whatsoever to those of this License. provided that this License. You may also lend copies. VERBATIM COPYING You may copy and distribute the Document in any medium. the title page itself. such as “Acknowledgements”. 2. you may accept compensation in exchange for copies. The front cover must present the full title with all words of the title equally prominent and visible. all these Cover Texts: Front-Cover Texts on the front cover.

If you use the latter option. you must take reasonably prudent steps. In addition. and from those of previous versions (which should. that you contact the authors of the Document well before redistributing any large number of copies. State on the Title page the name of the publisher of the Modified Version. E. one or more persons or entities responsible for authorship of the modifications in the Modified Version. unless they release you from this requirement. you should put the first ones listed (as many as fit reasonably) on the actual cover. Preserve all the copyright notices of the Document. You may use the same title as a previous version if the original publisher of that version gives permission. if it has fewer than five). or state in or with each Opaque copy a computernetwork location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document. It is requested. you must either include a machine-readable Transparent copy along with each Opaque copy. GNU FREE DOCUMENTATION LICENSE If the required texts for either cover are too voluminous to fit legibly. when you begin distribution of Opaque copies in quantity. B. be listed in the History section of the Document). 4. C. D. together with at least five of the principal authors of the Document (all of its principal authors. thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. Use in the Title Page (and on the covers. List on the Title Page. if any) a title distinct from that of the Document. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above. to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. and continue the rest onto adjacent pages. provided that you release the Modified Version under precisely this License. to give them a chance to provide you with an updated version of the Document. with the Modified Version filling the role of the Document. If you publish or distribute Opaque copies of the Document numbering more than 100.122 APPENDIX D. as authors. as the publisher. but not required. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. . free of added material. you must do these things in the Modified Version: A. if there were any.

. Preserve its Title. new authors. and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. a license notice giving the public permission to use the Modified Version under the terms of this License. These may be placed in the “History” section. Include an unaltered copy of this License. I. Such a section may not be included in the Modified Version. K. Preserve any Warranty Disclaimers. unaltered in their text and in their titles. H. and add to it an item stating at least the title. add their titles to the list of Invariant Sections in the Modified Version’s license notice. given in the Document for public access to a Transparent copy of the Document. N. Preserve the Title of the section. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document’s license notice. These titles must be distinct from any other section titles. O. To do this. For any section Entitled “Acknowledgements” or “Dedications”. Preserve the network location. Do not retitle any existing section to be Entitled “Endorsements” or to conflict in title with any Invariant Section. provided it contains nothing but endorsements of your Modified Version by various parties–for example. and publisher of the Modified Version as given on the Title Page. Section numbers or the equivalent are not considered part of the section titles. You may add a section Entitled “Endorsements”. or if the original publisher of the version it refers to gives permission. Preserve all the Invariant Sections of the Document. Include. Delete any section Entitled “Endorsements”. M. create one stating the title. Preserve the section Entitled “History”. and publisher of the Document as given on its Title Page. If there is no section Entitled “History” in the Document. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document. You may omit a network location for a work that was published at least four years before the Document itself. J. year. you may at your option designate some or all of these sections as invariant. year. in the form shown in the Addendum below. if any. authors.123 F. then add an item describing the Modified Version as stated in the previous sentence. immediately after the copyright notices. L. G. and likewise the network locations given in the Document for previous versions it was based on.

You may add a passage of up to five words as a Front-Cover Text. to the end of the list of Cover Texts in the Modified Version. in parentheses. and follow this License in all other respects regarding verbatim copying of that document. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. you must combine any sections Entitled “History” in the various original documents.124 APPENDIX D. and multiple identical Invariant Sections may be replaced with a single copy. The combined work need only contain one copy of this License. provided you insert a copy of this License into the extracted document. and replace the individual copies of this License in the various documents with a single copy that is included in the collection. COMBINING DOCUMENTS You may combine the Document with other documents released under this License. and distribute it individually under this License. you may not add another. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. previously added by you or by arrangement made by the same entity you are acting on behalf of. and list them all as Invariant Sections of your combined work in its license notice. on explicit permission from the previous publisher that added the old one. provided that you include in the combination all of the Invariant Sections of all of the original documents. and that you preserve all their Warranty Disclaimers. or else a unique number. likewise combine any sections Entitled “Acknowledgements”. In the combination. forming one section Entitled “History”. You must delete all sections Entitled “Endorsements”. and a passage of up to 25 words as a Back-Cover Text. If there are multiple Invariant Sections with the same name but different contents. make the title of each such section unique by adding at the end of it. under the terms defined in section 4 above for modified versions. provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. 5. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. If the Document already includes a cover text for the same cover. the name of the original author or publisher of that section if known. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License. unmodified. . and any sections Entitled “Dedications”. You may extract a single document from such a collection. but you may replace the old one. GNU FREE DOCUMENTATION LICENSE statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. 6.

is called an “aggregate” if the copyright resulting from the compilation is not used to limit the legal rights of the compilation’s users beyond what the individual works permit. the original version will prevail. or the electronic equivalent of covers if the Document is in electronic form. in or on a volume of a storage or distribution medium. 9. When the Document is included in an aggregate. revised versions of the GNU Free Documentation License from time to time. You may include a translation of this License. TERMINATION You may not copy. so you may distribute translations of the Document under the terms of section 4. 10. Any other attempt to copy. provided that you also include the original English version of this License and the original versions of those notices and disclaimers. Replacing Invariant Sections with translations requires special permission from their copyright holders. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer. If a section in the Document is Entitled “Acknowledgements”. then if the Document is less than one half of the entire aggregate. and will automatically terminate your rights under this License. this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. TRANSLATION Translation is considered a kind of modification. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new. parties who have received copies. or “History”. and any Warranty Disclaimers. sublicense. However. and all the license notices in the Document. 8. modify. but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. modify. from you under this License will not have their licenses terminated so long as such parties remain in full compliance. “Dedications”. or distribute the Document except as expressly provided for under this License. Such new versions will be .125 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works. If the Cover Text requirement of section 3 is applicable to these copies of the Document. the Document’s Cover Texts may be placed on covers that bracket the Document within the aggregate. or rights. sublicense or distribute the Document is void. Otherwise they must appear on printed covers that bracket the whole aggregate.

If your document contains nontrivial examples of program code.gnu. with the Front-Cover Texts being LIST. and no Back-Cover Texts. . with no Invariant Sections. no Front-Cover Texts. A copy of the license is included in the section entitled “GNU Free Documentation License”.org/copyleft/. If you have Invariant Sections. Texts. merge those two alternatives to suit the situation. and with the Back-Cover Texts being LIST. GNU FREE DOCUMENTATION LICENSE similar in spirit to the present version. ADDENDUM: How to use this License for your documents To use this License in a document you have written. distribute and/or modify this document under the terms of the GNU Free Documentation License. Permission is granted to copy. See http://www.126 APPENDIX D. replace the “with . you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. Version 1. we recommend releasing these examples in parallel under your choice of free software license. If you have Invariant Sections without Cover Texts.2 or any later version published by the Free Software Foundation. Front-Cover Texts and Back-Cover Texts. If the Document specifies that a particular numbered version of this License “or any later version” applies to it. . such as the GNU General Public License. . If the Document does not specify a version number of this License. Each version of the License is given a distinguishing version number. include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright c YEAR YOUR NAME. to permit their use in free software.” line with this: with the Invariant Sections being LIST THEIR TITLES. but may differ in detail to address new problems or concerns. you may choose any version ever published (not as a draft) by the Free Software Foundation. or some other combination of the three.

30 Error. 44 equal?. 30. 54 -o. 17. 96. 42 car. 68. 97 continuation-passing style. 38. 58 >>. 77. 68 eval. 70 extendEnv. 96 equal?. 31. 33 evalString. 44 Error. 37. 69 and. 42 closePort. 32 addBinding.Index *. 17 -fglasgow-exts. 102 compose. 44. 29. 2 define. 9. 33. 50. 69 extractValue. 26. 29. 23. 31. 3. 8. 92 127 . 50. 50 eqvPair. 97 cond. 66. 69 define. 44 eq?. 69 bindVars. 49 eq?. 89 Composable Memory Transactions. 6. 33. 7. 30. 88 assoc. 3 -package. 21. 42 cons. 1 Either. 96 eqv. 23. 6 =>. 70 Eq. 55 flip. 66. 66 ErrorT. 32. 8 bindings. 101 evalAndPrint. 55 catamorphism. 80 defineVar. 96 Atom. 44 eqv?. 77. 30. 90 Bool. 80 digit. 94 catchError. 38. 70 evalBody. 96 eqvEquals. 96 $. 9. 9. 79 evaled. 30. 23. 93 +. 36 ->. 31. 33. 5 DottedList. 92 filter. 67 Env. 43 Eclipse. 93. 79. 54. 31 >>=. 21 #f. 66. 48–50. 101 curry. 67. 99 applyProc. 67 C. 96 case. 5. 94 append. 38 cons. 93 --make. v car. 54 boolBinop. 31 cdr. 49 catchError. 92 declarative language.

2 id. 6. 1 main. 102 Function Programming. 93. 18 HaskellDB. v number?. 87 load. 99 load. 18. 95–97 folds. 66 LispVal. 91 null. 93 Fudgets. 66. 1 getArgs. 66–68. 89 hGetLine. 92 ioPrimitives. 8. 78–80. 79. 97 foldl. 38. 96 min. 89 . 68. 89 LispVals. 66 IOThrowsError. 24 Java. 50 initialList. 2 getArgs. 90 list-ref. 43 IORef. 24. 70. 93–96 foldr. 30–32 numBoolBinop. 96. 8. 58. 65 I/O. 89 Hugs. v hypermorphism. 32 letter. 89. 65 interact. 95 mem-helper. 66. 89. 57.128 fold. 95 IORefs. 1 LispError. 9 map. 5. 33. 9 IOThrows. 99 list-tail. 87 Haskell 98 Report. 67 monad. 95 Monad. 87. 30. 89 liftThrows. 44. 20. 20. 88 makeNormalFunc. 66 openFile. 58 Monad. 67. 89 negative?. 78. 5 MonadError. 70 Number. 17. 32 makeFunc. 18. 69 max. 91 90 nullEnv. 31. 38 List. v in. 8. 79 makeVarArgs. 94 INDEX liftIO. 2. 92 IOMode. 31. 95. 32. 80. 89 hPrint. 78. 24. 17. 4 getVar. 54 imperative languages. 9. 79 many. 67 myStateAction. 77. 5 oneOf. 88 Nil. 24. 102 Handle. 90 Main. 67 GHC. 5 lexme. 7 lifting. 30. 21. v. 102 hClose. 101 numericBinop. 87. 66 noneOf. 27 Left. 91–93. 1. 18 Haskell Prelude. 49. 1. 37 let-binding. 89 Linux. 96 member. 58 IO. not. 2. 66–69 nil. 21 getLine. 97 mapM. 2–4. 96 if. 29–31. 91. 70. 32. 89 NIL. 65 Integer. 65. 48. 78.

89 record. v. 4. 95 Right. 88 readProc. 89. 94 symbol?. 49 unpacker. v R5RS Scheme. 4 readAll.INDEX or. 17 varArgs. 32. v sum. 77. 1 The Little Schemer. 90 runRepl. 21. 94 putStrLn. 31 parseExpr. 32 runErrorT. 24. 91 Visual Studio. 89 showVal. 87 positive?. 7 spaces. 2 Python. 65 STDERR. 65 State. 6 ParseError. 89 show. 18 unwordsList. 9. 89 port. 1 writeIORef. 67 throwError. 37 unpackNum. 92 primitiveBindings. 21. v. 66. 30. 31. 27 Structure and Interpretation of Computer Programs. 70 runOne. 38 unwords. 65 Show. 18 129 show. 33. 8 Software Transactional Memory. 44 Windows. 101 where. 8 SQL. 32 unfold. 6. 101 Unpacker. 6 Parsec. 70. 89 readOrThrow. 18. 70. 94 parse. v throwError. 88 readFile. 90 String. 79. 30–32. 33. 20. 87 product. 31. 93 referential transparency. 27. 30 parse. 7. 102 space. 57 TypeMismatch. 102 ST. 68 ThrowsError. 68 WriteMode. 89 readExpr. 20. 5. 91 UNIX. 70. 5. 9 parseString. 77. 67 try. 27 System. 7. 18. 58 return. 88 readExprList. 27. 65. 33. 33 putStrln. 79. 89 readIORef. 90 readContents. 2 repeat. 79. 67 reverse. 89 Parser. 77 reduce. 89 skipMany1. 79. 89 . 17 Port. 9 pattern mataching. 88 PrimitiveFunc. 1 WASH. 1. 90 STDOUT. 31. 24 unpackNum. 67. 7. 90 string?. 101 read. 2. 68 ReadMode. 21. 5. 8. 89 writeProc. 49. 79 runState. 67 runIOThrows. 18. 29. 89 trapError. 6. 87.

130 wxHaskell. 102 zero?. 92 INDEX .