Write Yourself a Scheme in 48 Hours

  • Upload
    jsennek

  • View
    228

  • Download
    0

Embed Size (px)

Citation preview

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    1/138

    Write Yourself a Scheme in 48 HoursAn Introduction to Haskell through Example

    by Wikibooks contributors

    originally by Jonathan Tang

    Created on Wikibooks,the open content textbooks collection.

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    2/138

    Copyright c 2007 Jonathan Tang & Wikibooks contributors.

    Permission is granted to copy, distribute and/or modify this document underthe terms of the GNU Free Documentation License, Version 1.2 or any later ver-sion published by the Free Software Foundation; with no Invariant Sections, noFront-Cover Texts, and no Back-Cover Texts. A copy of the license is includedin the section entitled GNU Free Documentation License.

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    3/138

    Contents

    Overview v

    1 First Steps 1

    Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

    2 Parsing 5

    Writing a Simple Parser . . . . . . . . . . . . . . . . . . . . . . . . . . 5Whitespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6Return Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

    Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12Recursive Parsers: Adding lists, dotted lists, and quoted datums . . . 13

    Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

    3 Evaluation, Part 1 17

    Beginning the Evaluator . . . . . . . . . . . . . . . . . . . . . . . . . . 17Beginnings of an Evaluator: Primitives. . . . . . . . . . . . . . . . . . 20

    Adding Basic Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . 23Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

    4 Error Checking and Exceptions 29

    5 Evaluation, Part 2 37

    Additional Primitives: Partial Application . . . . . . . . . . . . . . . . 37Conditionals: Pattern Matching 2. . . . . . . . . . . . . . . . . . . . . 42List Primitives: car, cdr, and cons . . . . . . . . . . . . . . . . . . . 42Equal? and Weak Typing: Heterogenous Lists . . . . . . . . . . . . . 48Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

    6 Building a REPL 57

    7 Adding Variables and Assignment 65

    8 Defining Scheme Functions 77

    9 Creating IO Primitives 87

    i

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    4/138

    10 Towards a Standard Library 91

    Conclusion 101

    A Complete Parser 103

    B Answers to Exercises 113

    Section 2.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113Exercise 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

    Part 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113Part 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

    Exercise 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113Exercise 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114Exercise 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114Exercise 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

    C Document Information 117

    History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117PDF Information & History . . . . . . . . . . . . . . . . . . . . . . . . 117Document Formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

    Orignal Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118Wikibooks Changes . . . . . . . . . . . . . . . . . . . . . . . . . 118

    D GNU Free Documentation License 119

    1. APPLICABILITY AND DEFINITIONS . . . . . . . . . . . . . . . 1192. VERBATIM COPYING . . . . . . . . . . . . . . . . . . . . . . . . 1213. COPYING IN QUANTITY . . . . . . . . . . . . . . . . . . . . . . 121

    4. MODIFICATIONS . . . . . . . . . . . . . . . . . . . . . . . . . . . 1225. COMBINING DOCUMENTS . . . . . . . . . . . . . . . . . . . . . 1246. COLLECTIONS OF DOCUMENTS . . . . . . . . . . . . . . . . . 1247. AGGREGATION WITH INDEPENDENT WORKS . . . . . . . . 1258. TRANSLATION . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1259. TERMINATION . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12510. FUTURE REVISIONS OF THIS LICENSE . . . . . . . . . . . . 125ADDENDUM: How to use this License for your documents . . . . . . 1 2 6

    Index 127

    ii

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    5/138

    Listings

    1.1 hello.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1 simpleparser1.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . 62.2 simpleparser2.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.3 datatypeparser.hs. . . . . . . . . . . . . . . . . . . . . . . . . . . 112.4 recursiveparser.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . 143.1 evaluator1.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183.2 evaluator2.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213.3 evaluator3.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244.1 errorcheck.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335.1 operatorparser.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . 385.2 listparser.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445.3 equalparser.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506.1 replparser.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587.1 variableparser.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . 708.1 functionparser.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . 8010.1 stdlib.scm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

    A.1 completeparser.hs. . . . . . . . . . . . . . . . . . . . . . . . . . . 103

    iii

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    6/138

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    7/138

    Overview

    live version

    discussion

    edit

    comment

    report an error

    Most Haskell tutorials on the web seem to take a language-reference-manualapproach to teaching. They show you the syntax of the language, a few languageconstructs, and then have you construct a few simple functions at the interactiveprompt. The hard stuff of how to write a functioning, useful program is leftto the end, or sometimes omitted entirely.

    This tutorial takes a different tack. Youll start offwith command-line argu-ments and parsing, and progress to writing a fully-functional Scheme interpreterthat implements a good-sized subset of R5RS Scheme. Along the way, youlllearn Haskells I/O, mutable state, dynamic typing, error handling, and parsingfeatures. By the time you finish, you should be fairly fluent in both Haskell andScheme.

    Therere two main audiences targetted by this tutorial:

    1. People who already knowLispor Schemeand want to learnHaskell

    2. People who dont know any programming language, but have a strongquantitative background and are familiar with computers

    The second group will likely find this challenging, as I gloss over severalScheme and general programming concepts to stay focused on the Haskell. Agood textbook like Structure and Interpretation of Computer Programsor TheLittle Schemermay help a lot here.

    Users of a procedural or object-oriented language like C, Java, or Pythonshould beware, however: Youll have to forget most of what you already knowabout programming. Haskell is completely different from those languages, andrequires a different way of thinking about programming. Its best to go intothis tutorial with a blank slate and try not to compare Haskell to imperativelanguages, because many concepts in them (classes, functions, return) have asignificantly different meaning in Haskell.

    Since each lesson builds on the code written for the previous one, its prob-

    ably best to go through the lessons in order.This tutorial assumes that youll be usingGHCas your Haskell compiler. It

    may work with eg. Hugs, but it hasnt been tested at all, and you may need todownload additional libraries.

    v

    http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours#Overviewhttp://en.wikibooks.org/wiki/Talk:Write_Yourself_a_Scheme_in_48_Hourshttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours&action=edit&section=1http://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours&action=edit&section=newhttp://www.schemers.org/Documents/Standards/R5RS/HTMLhttp://www.schemers.org/Documents/Standards/R5RS/HTMLhttp://en.wikipedia.org/wiki/Lisp_programming_languagehttp://en.wikipedia.org/wiki/Scheme_programming_languagehttp://en.wikipedia.org/wiki/Haskell_programming_languagehttp://mitpress.mit.edu/sicp/full-text/book/book.htmlhttp://www.ccs.neu.edu/home/matthias/BTLS/http://www.ccs.neu.edu/home/matthias/BTLS/http://www.haskell.org/ghc/http://www.haskell.org/hugs/http://www.haskell.org/hugs/http://www.haskell.org/ghc/http://www.ccs.neu.edu/home/matthias/BTLS/http://www.ccs.neu.edu/home/matthias/BTLS/http://mitpress.mit.edu/sicp/full-text/book/book.htmlhttp://en.wikipedia.org/wiki/Haskell_programming_languagehttp://en.wikipedia.org/wiki/Scheme_programming_languagehttp://en.wikipedia.org/wiki/Lisp_programming_languagehttp://www.schemers.org/Documents/Standards/R5RS/HTMLhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours&action=edit&section=1http://en.wikibooks.org/wiki/Talk:Write_Yourself_a_Scheme_in_48_Hourshttp://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours#Overview
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    8/138

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    9/138

    Chapter 1

    First Steps: Compiling andRunning

    live version

    discussion

    edit

    comment

    report an error

    First, youll need to install GHC. On Linux, its often pre-installed or availablevia apt-get or yum. Its also downloadable from http://www.haskell.org/ghc/. A binary package is probably easiest, unless you really know what youredoing. It should download and install like any other software package. Thistutorial was developed on Linux, but everything should also work on Windowsas long as you know how to use the DOS command line.

    For UNIX (orWindows Emacs) users, there is a pretty goodEmacs mode,including syntax highlighting and automatic indentation. Windows users canuse Notepad or any other text editor: Haskell syntax is fairly Notepad-friendly,though you have to be careful with the indentation. Eclipseusers might want

    to try theFunction Programmingplug-in. Finally, theres also a Haskell pluginfor Visual Studiousing the GHC compiler.

    Now, its time for your first Haskell program. This program will read a nameoffthe command line and then print a greeting. Create a file ending in .hs andtype the following text:

    Listing 1.1: Your first Haskell program (hello.hs)

    module Main whereimport System .Environment

    main : : IO ( )5 main = do a r g s

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    10/138

    2 CHAPTER 1. FIRST STEPS

    The line main :: IO() is a type declaration: it says that the action mainhastypeIO (). Type declarations in Haskell are optional: the compiler figures them

    out automatically, and only complains if they differ from what youve specified.In this tutorial, I specify the types of all declarations explicitly, for clarity. Ifyoure following along at home, you may want to omit them, because its lessto change as we build our program.

    The IO type is an instance of something called a monad, which is a scaryname for a not-so-scary concept. Basically, a monad is a way of saying theressome extra information attached to this value, which most functions dont needto worry about. In this example, the extra information is the fact that thisaction performs IO, and the basic value is nothing, represented as (). Monadicvalues are often called actions, because the easiest way to think about the IOmonad is a sequencing of actions that each might affect the outside world.

    Haskell is a declarative language: instead of giving the computer a sequenceof instructions to carry out, you give it a collection of definitions that tell ithow to perform every function it might need. These definitions use variouscompositions of actions and functions. The compiler figures out an executionpath that puts everything together.

    To write one of these definitions, you set it up as an equation. The lefthand side defines a name, and optionally one or more patterns (explained later)that will bind variables. The right hand side defines some composition of otherdefinitions that tells the computer what to do when it encounters the name.These equations behave just like ordinary equations in algebra: you can alwayssubstitute the right hand side for the left within the text of the program, and itllevaluate to the same value. Calledreferential transparency, this property makesit significantly easier to reason about Haskell programs than other languages.

    How will we define our main action? We know that it must be anIO action,

    and that we want it to read the command line args and print some output.There are two ways to create an IO action:

    1. Lift an ordinary value into the IO monad, using the return function.

    2. Combine two existingIO actions.

    Since we want to do two things, well take the second approach. The built-in action getArgs reads the command-line arguments and stores them in a listof strings. The built-in function putStrLn takes a string and writes it to theconsole.

    To combine them, we use a do-block. A do-block consists of a series of lines,all lined up with the first non-whitespace character after the do. Each line canhave one of two forms:

    1. name

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    11/138

    3

    of strings, as with getArgs), then name will be bound to the list of stringsreturned. The second form just executes the action, sequencing it with the

    previous line through the >> (pronounced bind) operator. This operator hasdifferent semantics for each monad: in the case of the IO monad, it executesthe actions sequentially, performing whatever external side-effects that result.Because the semantics of this composition depends upon the particular monadused, you cannot mix actions of different monad types in the same do-block.

    Of course, these actions may themselves be the result of functions or com-plicated expressions. In this example, we first take index 0 of the argument list(args !! 0), concatenate it onto the end of the string Hello, ("Hello, "++),and finally pass that to putStrLnfor IO sequencing. Strings are lists of char-acters in Haskell, so you can use any of the list functions and operators on them.A full table of the standard operators and their precedences follows:

    Table 1.1: Operators and their precedenceOperator(s) Precedence Associativity Description

    . 9 Right Function composition!! 9 Left List indexing, , 8 Right Exponentiation (integer, frac-

    tional, and floating-point), / 7 Left Multiplication, Division+, 6 Left Addition, Subtraction: 5 Right Cons (list construction)++ 5 Right List Concatenationelem,notElem

    4 Left List Membership

    ==, /=,

    4 Left Equals, Not-equals, and other re-lation operators

    && 3 Right Logical And|| 2 Right Logical Or>>, >>= 1 Left Monadic Bind, Monadic Bind

    (piping value to next function)=> ghc o h e ll o yo u h e l lo. h suser>> . /h e l l o y o u J on at ha n

    H el l o , Jonathan

    The -o option specifies the name of the executable you want to create, andthen you just specify the name of the Haskell source file.

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    12/138

    4 CHAPTER 1. FIRST STEPS

    Exercises

    1. Change the program so it reads two arguments from the command line,and prints out a message using both of them.

    2. Change the program so it performs a simple arithmetic operation on thetwo arguments and prints out the result. You can use read to convert astring to a number, andshowto convert a number back into a string. Playaround with different operations.

    3. getLine is an IO action that reads a line from the console and returnsit as a string. Change the program so it prompts for a name, reads thename, and then prints that instead of the command line value.

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    13/138

    Chapter 2

    Parsing

    live version discussion

    edit

    comment

    report an error

    Writing a Simple Parser

    [edit section]

    Now, lets try writing a very simple parser. Well be using the Parseclibrary,which comes with GHC but may need to be downloaded separately if youreusing another compiler.

    Start by adding this line to the import section:

    3 import Text.Pa r s er Co m b i na t o r s. Pa r s ec hiding (s p a c e s)

    This makes the Parsec library functions available to us, except the spacesfunction, whose name conflicts with a function that well be defining later.

    Now, well define a parser that recognizes one of the symbols allowed inScheme identifiers:

    9 symbol : : P a r s e r Char10 symbol = oneOf "!$%&|*+-/:@^_~#"

    This is another example of a monad: in this case, the extra informationthat is being hidden is all the info about position in the input stream, back-tracking record, first and follow sets, etc. Parsec takes care of all of that for us.We need only use the Parsec library function oneOf,and itll recognize a singleone of any of the characters in the string passed to it. Parsec provides a numberof pre-built parsers: for example, letterand digitare library functions. And asyoure about to see, you can compose primitive parsers into more sophisticatedproductions.

    Lets define a function to call our parser and handle any possible errors:

    12 readExpr : : String > String13 r ea d Ex pr i n p u t = case p a r s e s ym bo l "lisp" i n p u t of14 Left e r r > " No m at ch : " ++ show e r r15 Right v a l > " F o un d v a lu e "

    As you can see from the type signature, readExpr is a function (>) froma String to a String. We name the parameter input, and pass it, along with

    5

    http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Parsinghttp://en.wikibooks.org/wiki/Talk:Write_Yourself_a_Scheme_in_48_Hours/Parsinghttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edithttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=1http://www.cs.uu.nl/~daan/download/parsec/parsec.htmlhttp://www.haskell.org/ghchttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#oneOfhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#oneOfhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#letterhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#digithttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#digithttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#letterhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#oneOfhttp://www.haskell.org/ghchttp://www.cs.uu.nl/~daan/download/parsec/parsec.htmlhttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=1http://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edithttp://en.wikibooks.org/wiki/Talk:Write_Yourself_a_Scheme_in_48_Hours/Parsinghttp://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Parsing
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    14/138

    6 CHAPTER 2. PARSING

    the symbol action we defined above and the name of the parser (lisp), to theParsec function parse.

    parsecan return either the parsed value or an error, so we need to handlethe error case. Following typical Haskell convention, Parsec returns an Eitherdata type, using the Left constructor to indicate an error and the Right onefor a normal value.

    We use a case...of construction to match the result of parse against thesealternatives. If we get aLeft value (error), then we bind the error itself to errand return "No match" with the string representation of the error. If we get aRightvalue, we bind it to val, ignore it, and return the string "Found value".

    The case...of construction is an example of pattern matching, which we willsee in much greater detail later on.

    Finally, we need to change our main function to call readExprand print outthe result:

    5

    main : : IO ( )6 main = do a r g s Stringr e a d Ex p r i n p u t = case p a r s e s y mb o l " l i s p " i n p u t of

    Left e r r > " No m at ch : " ++ show e r r15 Right v a l > " F o un d v a lu e "

    To compile and run this, you need to specify -package parsec on the commandline, or else there will be link errors. For example:

    user>> ghc p a ck a ge p a r s e c o s i m p l e p ar s e r s i m p le p a r se r 1. h suser>> . /s i m pl e p a r se r $

    Found valu euser>> . /s i m pl e p a r se r a

    No match: "lisp" (l i n e 1 , column 1 ) :u n ex p ect ed "a"

    Whitespace[edit section]

    Next, well add a series of improvements to our parser thatll let it recognizeprogressively more complicated expressions. The current parser chokes if thereswhitespace preceding our symbol:

    http://www.cs.uu.nl/~daan/download/parsec/parsec.html#parsehttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#parsehttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=2http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=2http://www.cs.uu.nl/~daan/download/parsec/parsec.html#parse
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    15/138

    WHITESPACE 7

    user>> . /s i m p l e p a r s e r " %"No match: "lisp" (l i n e 1 , column 1 ) :

    u n ex p ect ed " "

    Lets fix that, so that we ignore whitespace.First, lets define a parser that recognizes any number of whitespace char-

    acters. Incidentally, this is why we included the hiding (spaces) clause whenwe imported Parsec: theres already a function spaces in that library, but itdoesnt quite do what we want it to. (For that matter, theres also a parser calledlexemethat does exactly what we want, but well ignore that for pedagogicalpurposes.)

    16 s p a c e s : : P a r s e r ( )17 s p a c e s = skipMany1 space

    Just as functions can be passed to functions, so can actions. Here we passthe Parser action spaceto the Parser action skipMany1, to get a Parser that

    will recognize one or more spaces.Now, lets edit our parse function so that it uses this new parser. Changes

    are highlighted:

    1 r ea d Ex pr i n p u t = case p a r s e (spaces >> symbol) "lisp" i n p u t of

    2 Left e r r > " No m at ch : " ++ show e r r3 Right v a l > " F o un d v a lu e "

    We touched briefly on the >> (bind) operator in lesson 2, where we men-tioned that it was used behind the scenes to combine the lines of a do-block.Here, we use it explicitly to combine our whitespace and symbol parsers. How-ever, bind has completely different semantics in the Parser and IO monads. Inthe Parser monad, bind means Attempt to match the first parser, then attemptto match the second with the remaining input, and fail if either fails. In gen-

    eral, bind will have wildly different effects in different monads; its intended asa general way to structure computations, and so needs to be general enough toaccommodate all the different types of computations. Read the documentationfor the monad to figure out precisely what it does.

    Listing 2.2: A simpe parsing program, now ignoring whitespace (sim-pleparser2.hs)module Main whereimport System .Environmentimport Text. P a r s e r C o m b i n a t o r s. P a r s e c hiding (s p a c e s)

    5 main : : IO ( )main = do a r g s > symbol ) " l i s p " i n p u t ofLeft e r r > " No m at ch : " ++ show e r rRight v a l > " F o un d v a lu e "

    15

    s p a c e s : : P a r s e r ( )s p a c e s = s k ip M an y 1 s p a c e

    http://www.cs.uu.nl/~daan/download/parsec/parsec.html#spaceshttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#lexemehttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#spacehttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#skipMany1http://www.cs.uu.nl/~daan/download/parsec/parsec.html#skipMany1http://www.cs.uu.nl/~daan/download/parsec/parsec.html#skipMany1http://www.cs.uu.nl/~daan/download/parsec/parsec.html#spacehttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#lexemehttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#spaces
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    16/138

    8 CHAPTER 2. PARSING

    Compile and run this code. Note that since we definedspaces in terms ofskipMany1, it will no longer recognize a plain old single character. Instead you

    have to precede a symbol with some whitespace. Well see how this is usefulshortly:

    user>> ghc p a ck a ge p a r s e c o s i m p l e p ar s e r s i m p le p a r se r 2. h suser>> . /s i m p l e p a r s e r " %" Found val ueuser>> . /s i m p l e p a r s e r %

    No match: "lisp" (l i n e 1 , column 1 ) :u n ex p ect ed "%"e x p e c t i n g s p a ce

    user>> . /s i m p l e p a r s e r " abc "No match: "lisp" (l i n e 1 , column 4 ) :u n ex p ect ed "a"e x p e c t i n g s p a ce

    Return Values[edit section]

    Right now, the parser doesnt do much of anythingit just tells us whether agiven string can be recognized or not. Generally, we want something more outof our parsers: we want them to convert the input into a data structure thatwe can traverse easily. In this section, we learn how to define a data type, andhow to modify our parser so that it returns this data type.

    First, we need to define a data type that can hold any Lisp value:

    21 data L i s p V a l = Atom String22 | L i s t [L i s p V a l]23 | D o t t e d L i s t [L i s p V a l] L i s p V a l

    24 | Number Integer25 | String String26 | Bool Bool

    This is an example of an algebraic data type: it defines a set of possiblevalues that a variable of type LispVal can hold. Each alternative (called aconstructor and separated by |) contains a tag for the constructor along withthe type of data that that constructor can hold. In this example, a LispValcan be:

    1. An Atom, which stores a String naming the atom

    2. A List, which stores a list of other LispVals (Haskell lists are denotedby brackets); also called a proper list

    3. A DottedList, representing the Scheme form (a b . c); also called animproper list. This stores a list of all elements but the last, and thenstores the last element as another field

    4. A Number, containing a Haskell Integer

    http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=3http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Parsing&action=edit&section=3
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    17/138

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    18/138

    10 CHAPTER 2. PARSING

    Here, we introduce another Parsec combinator, the choice operator . Thistries the first parser, then if it fails, tries the second. If either succeeds, then it

    returns the value returned by that parser. The first parser must fail before itconsumes any input: well see later how to implement backtracking.

    Once weve read the first character and the rest of the atom, we need to putthem together. The let statement defines a new variable atom. We use the listconcatenation operator ++ for this. Recall that first is just a single character,so we convert it into a singleton list by putting brackets around it. If wedwanted to create a list containing many elements, we need only separate themby commas.

    Then we use a case statement to determine which LispVal to create andreturn, matching against the literal strings for true and false. The otherwisealternative is a readability trick: it binds a variable named otherwise, whosevalue we ignore, and then always returns the value of atom

    Finally, we create one more parser, for numbers. This shows one more wayof dealing with monadic values:

    43 parseNumber : : P a r s e r L i s p Va l44 parseNumber = liftM ( Number . read ) $ many1 d i g i t

    Its easiest to read this backwards, since both function application ($) andfunction composition (.) associate to the right. The parsec combinatormany1matches one or more of its argument, so here were matching one or more digits.Wed like to construct a numberLispValfrom the resulting string, but we havea few type mismatches. First, we use the built-in function read to convert thatstring into a number. Then we pass the result to Number to get a LispVal.The function composition operator . creates a function that applies its rightargument and then passes the result to the left argument, so we use that to

    combine the two function applications.Unfortunately, the result ofmany1 digit is actually a Parser String, so ourcombinedNumber. readstill cant operate on it. We need a way to tell it to justoperate on the value inside the monad, giving us back a Parser LispVal. Thestandard function liftM does exactly that, so we apply liftM to our Number.read function, and then apply the result of that to our parser.

    We also have to import the Monad module up at the top of our program toget access to liftM:

    2 impo rt Monad

    This style of programmingrelying heavily on function composition, func-tion application, and passing functions to functionsis very common in Haskellcode. It often lets you express very complicated algorithms in a single line,

    breaking down intermediate steps into other functions that can be combined invarious ways. Unfortunately, it means that you often have to read Haskell codefrom right-to-left and keep careful track of the types. Well be seeing manymore examples throughout the rest of the tutorial, so hopefully youll get prettycomfortable with it.

    Lets create a parser that accepts either a string, a number, or an atom:

    http://www.cs.uu.nl/~daan/download/parsec/parsec.html#orhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#orhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#many1http://www.cs.uu.nl/~daan/download/parsec/parsec.html#many1http://www.cs.uu.nl/~daan/download/parsec/parsec.html#or
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    19/138

    RETURN VALUES 11

    45

    46 parseExpr : : P a r s e r L i s p Va l

    47 parseExpr = parseAtom48 p a r s e S t r i n g

    And edit readExpr so it calls our new parser:

    1 readExpr : : String > String2 r ea d Ex pr i n p u t = case p a r s e parseExpr "lisp" i n p u t of

    3 Left e r r > " No m at ch : " ++ show e r r4 Right > " F o un d v a lu e "

    The complete code is therefore:

    Listing 2.3: A parser able to handle data types (datatypeparser.hs)

    module Main whereimpor t Monadimport System .Environmentimport Text. P a r s e r C o m b i n a t o r s. P a r s e c hiding (s p a c e s)

    5

    main : : IO ( )main = do a r g s Stringr e a d Ex p r i n p u t = case p a r s e p a r se E x pr " l i s p " i n p u t of

    15 Left e r r > " No m at ch : " ++ show e r rRight v a l > " F o un d v a lu e "

    s p a c e s : : P a r s e r ( )s p a c e s = s k ip M an y 1 s p a c e

    20

    data L i s p V a l = Atom String| Li s t [L i s p V a l]

    | D o t t e d L i s t [L i s p V a l] L i s p V a l| Number Integer

    25 | S tr i n g S tr i n g| Bool Bool

    p a r s e S t r i n g : : P a r s e r L i s p Va lp a r s e S t r i n g = do c h a r "

    30 x Atom atom

    parseNumber : : P a r s e r L i s p Va lparseNumber = l i f tM ( Number . read ) $ m any1 d i g i t

    45

    p a r s e E x p r : : P a r s e r L i s p Va lp a r s e E x p r = parseAtom

    p a r s e S t r i n g parseNumber

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    20/138

    12 CHAPTER 2. PARSING

    Compile and run this code, and youll notice that it accepts any number,string, or symbol, but not other strings:

    user>> ghc p a ck a ge p a r s e c o s i m p l e p a r s e r d a ta t y pe p a rs e r. h suser>> . /s i m p l e p a r s e r " \ " t hi s i s a s t ri n g \ ""

    Found valu euser>> . /s i m p l e p a r s e r 25

    Found valu euser>> . /s i m p l e p a r s e r s ym bo l

    Found valu euser>> . /s i m p l e p a r s e r ( symbol )

    bash: s y nt a x e r r o r n e ar u n ex pe c te d t o ke n symboluser>> . /s i m p l e p a r s e r "(symbol)"

    No match: "lisp" (l i n e 1 , column 1 ) :u n ex p ect ed "("e x p ec t i ng l e t t e r , "\"" o r d i g i t

    Exercises

    1. RewriteparseNumber using

    (a) do-notation.

    (b) explicit sequencing with the >>= operator.

    2. Our strings arent quiteR5RS compliant, because they dont support es-caping of internal quotes within the string. Change parseString so that\" gives a literal quote character instead of terminating the string. Youmay want to replace noneOf"\""with a new parser action that accepts

    either a non-quote character or a backslash followed by a quote mark.3. Modify the previous exercise to support \n, \r, \t, \\, and any other

    desired escape characters.

    4. Change parseNumber to support theScheme standard for different bases.You may find the readOct and readHex functions useful.

    5. Add a Character constructor toLispVal, and create a parser forcharacterliteralsas described in R5RS.

    6. Add a Float constructor to LispVal, and support R5RS syntax fordeci-mals. The Haskell function readFloat may be useful.

    7. Add data types and parsers to support the full numeric tower of Schemenumeric types. Haskell has built-in types to represent many of these;check the Prelude. For the others, you can define compound types thatrepresent eg. a Rational as a numerator and denominator, or a Complexas a real and imaginary part (each itself a Real number).

    http://www.haskell.org/onlinereport/standard-prelude.html#tMonadhttp://www.haskell.org/onlinereport/numeric.html#sect14http://www.haskell.org/onlinereport/numeric.html#sect14http://www.haskell.org/onlinereport/numeric.html#sect14http://www.haskell.org/onlinereport/numeric.html#sect14http://www.haskell.org/onlinereport/numeric.html#sect14http://www.haskell.org/onlinereport/numeric.html#sect14http://www.haskell.org/onlinereport/standard-prelude.html#tMonad
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    21/138

    RECURSIVE PARSERS: ADDING LISTS, DOTTED LISTS, AND QUOTED DATUMS13

    Recursive Parsers: Adding lists, dotted lists, and

    quoted datums [edit section]Next, we add a few more parser actions to our interpreter. Start with theparenthesized lists that make Lisp famous:

    46 p a r s e L i s t : : P a r s e r L i s p Va l47 p a r s e L i s t = li f tM L i s t $ s e pB y p a r se E x pr s p a c e s

    This works analogously to parseNumber, first parsing a series of expressionsseparated by whitespace (sepBy parseExpr spaces) and then apply the List con-structor to it within the Parser monad. Note too that we can pass parseExprto sepBy,even though its an action we wrote ourselves.

    The dotted-list parser is somewhat more complex, but still uses only conceptsthat were already familiar with:

    49 p a r s e D o t t e d L i s t : : P a r s e r L i s p Va l50 p a r s e D o t t e d L i s t = do51 head s p a c e s >> parseExpr53 return $ D o t te d L is t head t a i l

    Note how we can sequence together a series of Parser actions with >>andthen use the whole sequence on the right hand side of a do-statement. Theexpression char . >>spaces returns a Parser (), then combining that withparseExprgives a Parser LispVal, exactly the type we need for the do-block.

    Next, lets add support for the single-quote syntactic sugar of Scheme:

    55 parseQuoted : : P a r s e r L i s p Va l56 parseQuoted = do57 ch a r \ 58 x

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    22/138

    14 CHAPTER 2. PARSING

    This illustrates one last feature of Parsec: backtracking. parseList andparseDottedListrecognize identical strings up to the dot; this breaks the re-

    quirement that a choice alternative may not consume any input before failing.Thetry combinator attempts to run the specified parser, but if it fails, it backsup to the previous state. This lets you use it in a choice alternative withoutinterfering with the other alternative.

    The complete code is therefore:

    Listing 2.4: A simpe parser, now with Lisp list, dotted list and quoted datumparsing (recursiveparser.hs)module Main whereimpor t Monadimport System.Environmentimport Text. P a r s e r C o m b i n a t o r s. P a r s e c hiding (s p a c e s)

    5

    main : : IO ( )main = do a r g s " No m at ch : " ++ show e r r

    15 Right v a l > " F o un d v a l "

    s p a c e s : : P a r s e r ( )s p a c e s = s k ip Ma n y1 s p a c e

    20 data L i s p V a l = Atom String| L i s t [L i s p V a l]| D o t t e d L i s t [L i s p V a l] L i s p V a l| Number Integer| S tr i n g S tr i ng

    25 | Bool Bool

    p a r s e S t r i n g : : P a r s e r L i s p Va lp a r s e S t r i n g = do c h a r "

    30 x Atom atom

    parseNumber : : P a r s e r L i s p Va lparseNumber = l i f tM ( Number . read ) $ m any1 d i g i t

    45

    p a r s e L i s t : : P a r s e r L i s p Va lp a r s e L i s t = l if t M L i s t $ s e pB y p a r s e E x pr s p a c e s

    p a r s e D o t t e d L i s t : : P a r s e r L i s p Va l50 p a r s e D o t t e d L i s t = do

    head s p a c e s >> p a r s e E x p rreturn $ D o t t ed L i s t head t a i l

    http://www.cs.uu.nl/~daan/download/parsec/parsec.html#tryhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#try
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    23/138

    RECURSIVE PARSERS: ADDING LISTS, DOTTED LISTS, AND QUOTED DATUMS15

    55 p arseQu oted : : P a r s e r L i s p Va lp arseQu oted = do

    c h a r \ x ghc p a c ka g e p a r s e c o s i m pl e p a rs e r r e c u r s i ve p a r s e r. h suser>> . /s i m p l e p a r s e r " ( a t e st ) "

    Found valu euser>> . /s i m p l e p a r s e r " ( a ( n e s te d ) t e st ) "

    Found valu euser>> . /s i m p l e p a r s e r " ( a ( d ot te d . l is t ) t es t )"

    Found valu euser>> . /s i m p l e p a r s e r " ( a ( q uo t ed ( d o t te d . l i st ) ) t e st ) "

    Found valu euser>> . /s i m p l e p a r s e r " ( a ( i mb a la n c ed p a re n s ) "

    No match: "lisp" (l i n e 1 , column 24) :u n ex p ec t ed e nd o f i n p ute x p e ct i n g s pa c e o r ")"

    Note that by referring to parseExpr within our parsers, we can nest themarbitrarily deep. Thus, we get a full Lisp reader with only a few definitions.Thats the power of recursion.

    Exercises

    1. Add support for the backquote syntactic sugar: the Scheme standarddetails what it should expand into (quasiquote/unquote).

    2. Add support for vectors. The Haskell representation is up to you: GHCdoes have an Array data type, but it can be difficult to use. Strictlyspeaking, a vector should have constant-time indexing and updating, butdestructive update in a purely functional language is difficult. You mayhave a better idea how to do this after the section on set!, later in thistutorial.

    3. Instead of using the try combinator, left-factor the grammar so that the

    common subsequence is its own parser. You should end up with a parserthat matches a string of expressions, and one that matches either nothingor a dot and a single expressions. Combining the return values of theseinto either aList or aDottedList is left as a (somewhat tricky) exercisefor the reader: you may want to break it out into another helper function.

    http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-Array.htmlhttp://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-Array.html
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    24/138

    16 CHAPTER 2. PARSING

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    25/138

    Chapter 3

    Evaluation, Part 1

    live version discussion

    edit

    comment

    report an error

    Beginning the Evaluator

    [edit section]

    Currently, weve just been printing out whether or not we recognize the givenprogram fragment. Were about to take the first steps towards a working Schemeinterpreter: assigning values to program fragments. Well be starting with babysteps, but fairly soon youll be progressing to doing working computations.

    Lets start by telling Haskell how to print out a string representation of thevarious possible LispVals:

    70 showVal : : L i s p V a l > String71 showVal ( String c o n t e n t s) = "\"" ++ c o n t e n t s ++ "\""72 showVal (Atom name) = name73 showVal ( Number con ten ts) = show c o n t e n t s74 showVal ( Bool True) = "#t"75 showVal ( Bool False ) = "#f"

    This is our first real introduction to pattern matching. Pattern matching isa way of destructuring an algebraic data type, selecting a code clause based onits constructor and then binding the components to variables. Any constructorcan appear in a pattern; that pattern matches a value if the tag is the sameas the values tag and all subpatterns match their corresponding components.Patterns can be nested arbitrarily deep, with matching proceeding in an inside>outside, left >right order. The clauses of a function definition are tried intextual order, until one of the patterns matches. If this is confusing, youll seesome examples of deeply-nested patterns when we get further into the evaluator.

    For now, you only need to know that each clause of the above definition

    matches one of the constructors ofLispVal, and the right-hand side tells whatto do for a value of that constructor.

    The List and DottedList clauses work similarly, but we need to define ahelper function unwordsList to convert the contained list into a string:

    76 showVal ( L i s t c o n t e n t s) = "(" ++ un w o r d sL i s t c o n t e n t s ++ ") "

    17

    http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1http://en.wikibooks.org/wiki/Talk:Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edithttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=1http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=1http://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edithttp://en.wikibooks.org/wiki/Talk:Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    26/138

    18 CHAPTER 3. EVALUATION, PART 1

    77 showVal (D o t t e d L i s t head t a i l ) = "(" ++ u n wo r d s L i s t head ++ ". " ++ showVal t a i l ++ ") "

    The unwordsList function works like the Haskell Preludes unwords func-tion, which glues together a list of words with spaces. Since were dealing witha list ofLispVals instead of words, we define a function that first converts theLispVals into their string representations and then applies unwords to it:

    79 u n wo r d s L i s t : : [L i s p V a l] > String80 u n wo r d s L i s t = unwords . map showVal

    Our definition ofunwordsList doesnt include any arguments. This is anexample of point-free style: writing definitions purely in terms of function com-position and partial application, without regard to individual values or points.Instead, we define it as the composition of a couple built-in functions. First,we partially-apply map to showVal, which creates a function that takes a list of

    LispVals and returns a list of their string representations. Haskell functionsare curried: this means that a function of two arguments, like map, is really afunction that returns a function of one argument. As a result, if you supplyonly a single argument, you get back a function one argument that you can passaround, compose, and apply later. In this case, we compose it with unwords:mapshowValconverts a list ofLispValsto a list of theirStringrepresentations,and then unwords joins the result together with spaces.

    We used the function show above. This standard Haskell function lets youconvert any type thats an instance of the class Show into a string. Wed like tobe able to do the same with LispVal, so we make it into a member of the classShow, defining its show method as showVal:

    82 ins tanc e Show L i s p V a l where show = showVal

    A full treatment of typeclasses is beyond the scope of this tutorial; you canfind more information inother tutorialsand theHaskell 98 report.

    Lets try things out by changing our readExpr function so it returns thestring representation of the value actually parsed, instead of just "Found value":

    13 r ea d Ex pr i n p u t = case p a r s e p a r se E x pr "lisp" i n p u t of14 Left e r r > " No m at ch : " ++ show e r r15 Right v a l > " F ou nd " ++ show v a l

    The complete code is therefore:

    Listing 3.1: A parser using pattern matching (evaluator1.hs)

    module Main whereimpor t Monad

    import System.Environmentimport Text. P a r s e r C o m b i n a t o r s. P a r s e c hiding (s p a c e s)

    5

    main : : IO ( )main = do a r g s

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    27/138

    BEGINNING THE EVALUATOR 19

    symbol = oneOf " ! $ % & | * + -/ : < = ? > @ ^ _ ~ # "

    r e a d Ex p r i n p u t = case p a r s e p a r se E x pr " l i s p " i n p u t ofLeft e r r > " No m at ch : " ++ show e r r

    15 Right v a l > " F ou n d " ++ show v a l

    s p a c e s : : P a r s e r ( )s p a c e s = s k ip M an y 1 s p a c e

    20 data L i s p V a l = Atom String| Li s t [L i s p V a l]| D o t t e d L i s t [L i s p V a l] L i s p V a l| Number Integer| S tr i n g S tr i n g

    25 | Bool Bool

    p a r s e S t r i n g : : P a r s e r L i s p Va lp a r s e S t r i n g = do c h a r "

    x Atom atom

    parseNumber : : P a r s e r L i s p Va lparseNumber = l i f tM ( Number . read ) $ m any1 d i g i t

    45 p a r s e L i s t : : P a r s e r L i s p Va lp a r s e L i s t = l if t M L i s t $ s ep By p a r s eE x pr s p a c e s

    p a r s e D o t t e d L i s t : : P a r s e r L i s p Va lp a r s e D o t t e d L i s t = do

    50 head s p a c e s >> p a r s e E x p r

    return $ D o t t ed L i s t he ad t a i l

    p arseQu oted : : P a r s e r L i s p Va l55 p arseQu oted = do

    c h a r \ x

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    28/138

    20 CHAPTER 3. EVALUATION, PART 1

    u n w o r d s L i s t : : [L i s p V a l] > String80 u n w o r d s L i s t = unwords . map showVal

    inst ance Show L i s p V a l where show = showVal

    And compile and run...

    user>> ghc p a ck a ge p a r s e c o p a r s er e v a l ua t o r1.h suser>> . /p a r s e r " (1 2 2 )"

    Found ( 1 2 2 )user>> . /p a r s e r " ( 1 3 ( \ " th i s \" \ " o ne \ " ) ) "

    Found ( quote ( 1 3 ( " t h is " " o ne " ) ) )

    Beginnings of an Evaluator: Primitives[edit section]

    Now, we start with the beginnings of an evaluator. The purpose of an evaluatoris to map some code data type into some data data type, the result of theevaluation. In Lisp, the data types for both code and data are the same, so ourevaluator will return a LispVal. Other languages often have more complicatedcode structures, with a variety of syntactic forms.

    Evaluating numbers, strings, booleans, and quoted lists is fairly simple: re-turn the datum itself.

    85 e v a l : : L i s p V a l > L i s p V a l86 e v a l v al @ ( String ) = v a l87 e v a l v al @ ( Number ) = v a l88 e v a l v al @ ( Bool ) = v a l89 e v a l ( List [ Atom "quote", v a l] ) = v a l

    This introduces a new type of pattern. The notation val@(String )matchesagainst anyLispValthats a string and then binds val to the wholeLispVal, andnot just the contents of the String constructor. The result has type LispValinstead of type String. The underbar is the dont care variable, matchingany value yet not binding it to a variable. It can be used in any pattern, but ismost useful with @-patterns (where you bind the variable to the whole pattern)and with simple constructor-tests where youre just interested in the tag of theconstructor.

    The last clause is our first introduction to nested patterns. The type of datacontained byListis [LispVal], a list ofLispVals. We match that against thespecific two-element list [Atom "quote",val], a list where the first element is thesymbol "quote"and the second element can be anything. Then we return thatsecond element.

    Lets integrate eval into our existing code. Start by changing readExprbackso it returns the expression instead of a string representation of the expression:

    12 readExpr : : String > L i s p V a l13 r ea d Ex pr i n p u t = case p a r s e p a r se E x pr "lisp" i n p u t of14 Left e r r > String $ " N o m at ch : " ++ show e r r15 Right v a l > v a l

    http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=2http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=2
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    29/138

    BEGINNINGS OF AN EVALUATOR: PRIMITIVES 21

    And then change ourmainfunction to read an expression, evaluate it, convertit to a string, and print it out. Now that we know about the >>= monad

    sequencing operator and the function composition operator, lets use them tomake this a bit more concise:

    6 main : : IO ( )7 main = getArgs >>= putStrLn . show . e v a l . readExpr . ( ! ! 0 )

    Here, we take the result of the getArgs action (a list ofstrings) and passit into the composition of:

    1. Take the first value ( (!! 0)). This notation is known as an operator section:its telling the compiler to partially-apply the list indexing operator to 0,giving you back a function that takes the first element of whatever list itspassed.

    2. Parse it (readExpr)3. Evaluate it (eval)

    4. Convert it to a string (show)

    5. Print it (putStrLn)

    The complete code is therefore:

    Listing 3.2: The evaluator skeleton (evaluator2.hs)

    module Main whereimpor t Monadimport System .Environmentimport Text. P a r s e r C o m b i n a t o r s. P a r s e c hiding (s p a c e s)

    5

    main : : IO ( )main = getArgs >>= putStrLn . show . e v a l . read E xp r . ( ! ! 0 )

    symbol : : P a r s e r Char10 symbol = oneOf " ! $ % & | * + -/ : < = ? > @ ^ _ ~ # "

    read E xp r : : String > L i s p V a lr e a d Ex p r i n p u t = case p a r s e p a r se E x pr " l i s p " i n p u t of

    Left e r r > String $ " N o m at ch : " ++ show e r r15 Right v a l > v a l

    s p a c e s : : P a r s e r ( )s p a c e s = s k ip M an y 1 s p a c e

    20

    data L i s p V a l = Atom String| Li s t [L i s p V a l]| D o t t e d L i s t [L i s p V a l] L i s p V a l| Number Integer

    25 | S tr i n g S tr i n g| Bool Bool

    p a r s e S t r i n g : : P a r s e r L i s p Va lp a r s e S t r i n g = do c h a r "

    30 x

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    30/138

    22 CHAPTER 3. EVALUATION, PART 1

    parseAtom : : P a r s e r L i s p Va l35 parseAtom = do f i r s t Bool False

    otherwise > Atom atom

    parseNumber : : P a r s e r L i s p Va lparseNumber = l i f tM ( Number . read ) $ m any1 d i g i t

    45

    p a r s e L i s t : : P a r s e r L i s p Va lp a r s e L i s t = l if t M L i s t $ s e pB y p a r s e E x pr s p a c e s

    p a r s e D o t t e d L i s t : : P a r s e r L i s p Va l50 p a r s e D o t t e d L i s t = do

    head s p a c e s >> p a r s e E x p rreturn $ D o t t ed L i s t head t a i l

    55 p arseQu oted : : P a r s e r L i s p Va l

    p arseQu oted = doc h a r \ x Stringu n w o r d s L i s t = unwords . map showVal

    inst ance Show L i s p V a l where show = showVal

    85 e v a l : : L i s p V a l > L i s p V a le v a l v al @ ( String ) = v a le v a l v al @ ( Number ) = v a le v a l v al @ ( Bool ) = v a le v a l ( L i s t [ Atom " q u o t e ", v a l] ) = v a l

    Compile and run the code the normal way:

    user>> ghc p a ck a ge p a r s e c o e v a l e v a l ua t o r2.h suser>> . /e v a l "atom"

    atomuser>> . /e v a l 2

    2user>> . /e v a l " \ " a s t ri n g \" "

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    31/138

    ADDING BASIC PRIMITIVES 23

    " a s t ri n g "

    user>> . /e v a l " (+ 2 2 )"

    F a i l: e v a l. h s: 8 3 : None x h au s ti v e p a t te r n s i n f u n c t io n e v a l

    We still cant do all that much useful with the program (witness the failed(+ 2 2) call), but the basic skeleton is in place. Soon, well be extending it withsome functions to make it useful.

    Adding Basic Primitives[edit section]

    Next, well improve our Scheme so we can use it as a simple calculator. Its stillnot yet a programming language, but its getting close.

    Begin by adding a clause toevalto handle function application. Rememberthat all clauses of a function definition must be placed together and are evaluatedin textual order, so this should go after the other eval clauses:

    89 e v a l ( List (Atom fun c : a r g s) ) = ap pl y f u nc $ map e va l a r gs

    This is another nested pattern, but this time we match against the consoperator : instead of a literal list. Lists in Haskell are really syntactic sugar fora change of cons applications and the empty list: [1, 2, 3, 4] = 1:(2:(3:(4:[]) )). By pattern-matching against cons itself instead of a literal list, were sayinggive me the rest of the list instead of give me the second element of the list.For example, if we passed (+ 2 2) to eval, func would be bound to +and argswould be bound to [Number2, Number2].

    The rest of the clause consists of a couple functions weve seen before andone we havent defined yet. We have to recursively evaluate each argument, so

    we map eval over the args. This is what lets us write compound expressions like(+ 2 (3 1) ( 5 4)). Then we take the resulting list of evaluated arguments,and pass it and the original function to apply:

    91 apply : : String > [L i s p V a l] > L i s p V a l92 a p pl 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

    The built-in function lookup looks up a key (its first argument) in a list ofpairs. However, lookup will fail if no pair in the list contains the matching key.To express this, it returns an instance of the built-in type Maybe. We use thefunction maybe to specify what to do in case of either success or failure. If thefunction isnt found, we return a Bool False value, equivalent to #f(well addmore robust error-checking later). If it is found, we apply it to the arguments

    using ($ args), an operator section of the function application operator.Next, we define the list of primitives that we support:

    94 p r i m i t i v e s : : [ ( String, [L i s p V a l] > L i s p V a l) ]95 p r i m i t i v e s = [ (" +", numericBinop (+)) ,96 ( " -" , numericBinop () ) ,97 ( "*", numericBinop ( ) ) ,

    http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=3http://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Evaluation%2C_Part_1&action=edit&section=3
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    32/138

    24 CHAPTER 3. EVALUATION, PART 1

    98 ( "/ ", numericBinop di v ) ,99 ( "mod", numericBinop mod) ,

    100 ( "quotient", numericBinop quot ) ,101 ( "remainder", numericBinop rem) ]

    Look at the type of primitives. It is a list of pairs, just like lookup expects,but the values of the pairs are functions from[LispVal]toLispVal. In Haskell,you can easily store functions in other data structures, though the functionsmust all have the same type.

    Also, the functions that we store are themselves the result of a function,numericBinop, which we havent defined yet. This takes a primitive Haskellfunction (often an operator section) and wraps it with code to unpack an ar-gument list, apply the function to it, and wrap the result up in our Numberconstructor.

    103 numericBinop : : ( Integer > Integer > Integer ) > [L i s p V a l]> L i s p V a l

    104 numericBinop op params = Number $ f o l d l 1 op $ map unpackNumparams

    105

    106 unpackNum : : L i s p V a l > Integer107 unpackNum ( Number n ) = n108 unpackNum ( String n ) = l e t p a r s ed = reads n in109 i f n ul l p a r s ed110 then 0111 e ls e f s t $ p a rs e d ! ! 0112 unpackNum ( L i s t [ n ] ) = unpackNum n113 unpackNum = 0

    As with R5RS Scheme, we dont limit ourselves to only two arguments. Our

    numeric operations can work on a list of any length, so (+ 2 3 4) = 2 + 3 + 4,and ( 15 5 3 2) = 15 5 3 2. We use the built-in function foldl1 to dothis. It essentially changes every cons operator in the list to the binary functionwe supply, op.

    Unlike R5RS Scheme, were implementing a form of weak typing. Thatmeans that if a value can be interpreted as a number (like the string "2"), welluse it as one, even if its tagged as a string. We do this by adding a coupleextra clauses to unpackNum. If were unpacking a string, attempt to parse itwith Haskells built-in reads function, which returns a list of pairs of (parsedvalue, original value).

    For lists, we pattern-match against the one-element list and try to unpackthat. Anything else falls through to the next case.

    If we cant parse the number, for any reason, well return 0 for now. Well

    fix this shortly so that it signals an error.The complete code is therefore:

    Listing 3.3: A basic evaluator (evaluator3.hs)

    module Main whereimpor t Monad

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    33/138

    ADDING BASIC PRIMITIVES 25

    import System .Environmentimport Text. P a r s e r C o m b i n a t o r s. P a r s e c hiding (s p a c e s)

    5

    main : : IO ( )main = getArgs >>= putStrLn . show . e v a l . read E xp r . ( ! ! 0 )

    symbol : : P a r s e r Char10 symbol = oneOf " ! $ % & | * + - / : < = ? > @ ^ _ ~ "

    read E xp r : : String > L i s p V a lr e a d Ex p r i n p u t = case p a r s e p a r se E x pr " l i s p " i n p u t of

    Left e r r > String $ " N o m at ch : " ++ show e r r15 Right v a l > v a l

    s p a c e s : : P a r s e r ( )s p a c e s = s k ip M an y 1 s p a c e

    20 data L i s p V a l = Atom String| Li s t [L i s p V a l]| D o t t e d L i s t [L i s p V a l] L i s p V a l| Number Integer| S tr i n g S tr i n g

    25 | Bool Bool

    p a r s e S t r i n g : : P a r s e r L i s p Va lp a r s e S t r i n g = do c h a r "

    x Atom atom

    parseNumber : : P a r s e r L i s p Va lparseNumber = l i f tM ( Number . read ) $ m any1 d i g i t

    45 p a r s e L i s t : : P a r s e r L i s p Va lp a r s e L i s t = l if t M L i s t $ s ep By p a r s eE x pr s p a c e s

    p a r s e D o t t e d L i s t : : P a r s e r L i s p Va lp a r s e D o t t e d L i s t = do

    50 head s p a c e s >> p a r s e E x p rreturn $ D o t t ed L i s t he ad t a i l

    p arseQu oted : : P a r s e r L i s p Va l55 p arseQu oted = do

    c h a r \ x

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    34/138

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    35/138

    EXERCISES 27

    Exercises

    1. Add primitives to perform the various type-testing functions of R5RS:symbol?, string?, number?, etc.

    2. ChangeunpackNumso that it always returns 0 if the value is not a number,even if its a string or list that could be parsed as a number.

    3. Add thesymbol-handling functionsfrom R5RS. A symbol is what wevebeen calling an Atom in our data constructors.

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    36/138

    28 CHAPTER 3. EVALUATION, PART 1

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    37/138

    Chapter 4

    Error Checking andExceptions

    live version

    discussion

    edit

    comment

    report an error

    Currently, there are a variety of places within the code where we either ignoreerrors or silently assign default values like \#for 0 that make no sense. Somelanguages - like Perl and PHP - get along fine with this approach. However, itoften means that errors pass silently throughout the program until they becomebig problems, which means rather inconvenient debugging sessions for the pro-grammer. Wed like to signal errors as soon as they happen and immediatelybreak out of execution.

    First, we need to importControl.Monad.Error to get access to Haskellsbuilt-in error functions:

    4 import Co n t r o l. Monad.Er r o r

    Then, we should define a data type to represent an error:

    123 data L i s p E r r o r = NumArgs Integer [L i s p V a l]124 | TypeMismatch String L i s p V a l125 | P a r s e r P a r s e E r r o r126 | BadSpecialForm String L i s p V a l127 | NotFunction String String128 | UnboundVar String String129 | D e f a u l t String

    This is a few more constructors than we need at the moment, but we might aswell forsee all the other things that can go wrong in the interpreter later. Next,we define how to print out the various types of errors and make LispError an

    instance of Show:131 showError : : L i s p E r r o r > String132 showError ( UnboundVar message varname ) = message ++ ": " ++

    varname133 showError ( Ba d Sp ecia l Fo r m m es s ag e f o rm ) = message ++ ": " ++

    show form

    29

    http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptionshttp://en.wikibooks.org/wiki/Talk:Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptionshttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptions&action=edithttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptions&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptions&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptions&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Talk:Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptions&action=edit&section=newhttp://en.wikibooks.org/w/index.php?title=Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptions&action=edithttp://en.wikibooks.org/wiki/Talk:Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptionshttp://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Error_Checking_and_Exceptions
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    38/138

    30 CHAPTER 4. ERROR CHECKING AND EXCEPTIONS

    134 showError ( N o t Fu n ct io n m es sa g e f u n c) = message ++ ": " ++ showf u n c

    135 showError ( NumArgs expe cted found ) = " E x pe c te d " ++ showex p ect ed

    136 ++ " a rg s : f ou nd v al ue s " ++u n wo r d s L i s t f o u n d

    137 showError ( TypeMismatch e xpec ted found ) = " I n v al i d t y pe :e x pe c te d " ++ e x p e c t e d

    138 ++ " , f ou nd " ++ showfound

    139 showError (P a r s er p a r s eE r r) = " Pa rs e e rr or a t " ++ showp a r s e E r r

    140

    141 ins tanc e Show L i s p E r r o r where show = showError

    Our next step is to make our error type into an instance ofError. This is

    necessary for it to work with GHCs built-in error handling functions. Beingan instance of error just means that it must provide functions to create aninstance either from a previous error message or by itself:

    143 instance E r r or L i s p Er r o r where144 noMsg = D e f a u l t " A n e r ro r h a s o c cu r re d "145 strMsg = D e f a u l t

    Then we define a type to represent functions that may throw a LispErroror return a value. Remember how parseused anEitherdata type to representexceptions? 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 ap-

    plied. A full type would be Either LispError Integeror Either LispErrorLispVal, but we want to say ThrowsError LispVal and so on. We only par-tially apply Either to LispError, creating a type constructor ThrowsErrorthat we can use on any data type.

    Either is yet another instance of a monad. In this case, the extra informa-tion being passed between Eitheractions is whether or not an error occurred.Bind applies its function if the Either action holds a normal value, or passesan error straight through without computation. This is how exceptions work inother languages, but because Haskell is lazily-evaluated, theres no need for aseparate control-flow construct. Ifbind determines that a value is already anerror, the function is never called.

    The Either monad also provides two other functions besides the standardmonadic 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 anerror into another Either action. If the action represents an error, it

    http://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Error.htmlhttp://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Error.htmlhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#parsehttp://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#v%3athrowErrorhttp://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#v%3acatchErrorhttp://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#v%3acatchErrorhttp://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#v%3athrowErrorhttp://www.cs.uu.nl/~daan/download/parsec/parsec.html#parsehttp://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Error.html
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    39/138

    31

    applies the function, which you can use to eg. turn the error value into anormal one via return or re-throw as a different error.

    In our program, well be converting all of our errors to their string represen-tations and returning that as a normal value. Lets create a helper function todo that for us:

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

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

    151 e x t r a c t V a l u e : : ThrowsError a > a152 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 extractValueonly aftera catchError, so its better to fail fast than to inject bad values into the restof the program.

    Now that we have all the basic infrastructure, its time to start using ourerror-handling functions. Remember how our parser had previously just returna string saying "No match" on an error? Lets change it so that it wraps andthrows the original ParseError:

    16 readExpr : : String > Th r ows Err o r L i s p V a l17 r ea d Ex pr i n p u t = case p a r s e p a r se E x pr "lisp" i n p u t o f18 Left e r r > th r ow E rr o r $ P a r s er e r r19 Right v a l > return v a l

    Here, we first wrap the original ParseErrorwith the LispErrorconstructor

    Parser, and then use the built-in function throwError to return that in ourThrowsError monad. Since readExpr now returns a monadic value, we alsoneed to wrap the other case in a return function.

    Next, we change the type signature ofevalto return a monadic value, adjustthe return values accordingly, and add a clause to throw an error if we encountera pattern that we dont recognize:

    88 e v a l : : L i s p V a l > Th r ows Err o r L i s p V a l89 e v a l v al @ ( String ) = return v a l90 e v a l v al @ ( Number ) = return v a l91 e v a l v al @ ( Bool ) = return v a l92 e v a l ( List [ Atom "quote", v a l] ) = return v a l93 e v a l ( List (Atom fun c : a r g s) ) = mapM e v a l a r gs >>= a pp l y f u nc94 ev al badForm = t h r o wEr r or $ Ba d Sp ecia l F or m "Unrecognized

    special form" badForm

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

    http://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Error.html#v%3AthrowErrorhttp://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Error.html#v%3AthrowError
  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    40/138

    32 CHAPTER 4. ERROR CHECKING AND EXCEPTIONS

    Inside the Error monad, this sequencing performs all computations sequentiallybut throws an error value if any one of them failsgiving you Right [results]

    on success, or Left error on failure. Then, we used the monadic bind oper-ation to pass the result into the partially applied apply func, again returningan error if either operation failed.

    Next, we change apply itself so that it throws an error if it doesnt recognizethe function:

    96 a p p l y : : String > [L i s p V a l] > Th r ows Err o r L i s p V a l97 a pp ly f un c a r g s = maybe ( t h r o wEr r o r $ N o t Fu n cti o n "

    Unrecognized primitive function args" f u n c)98 ( $ a r g s)99 ( lookup fu nc p r i m i t i v e s)

    We didnt add a return statement to the function application ($ args).Were about to change the type of our primitives, so that the function returned

    from the lookup itself returns a ThrowsError action:

    101 p r i m i t i v e s : : [ ( String, [L i s p V a l] > Th r ows Err o r L i s p V a l) ]

    And, of course, we need to change the numericBinop function that imple-ments these primitives so it throws an error if theres only one argument:

    110 numericBinop : : ( Integer > Integer > Integer ) > [L i s p V a l]> Th r ows Err o r L i s p V a l

    111 n u m eri cBi no p o p s i n g l eV a l @[ ] = throwEr ror $ NumArgs 2s i n g l e V a l

    112 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, werelooking for a list of exactly one element, and we dont care what that elementis. We also need to use mapMto sequence the results ofunpackNum, because eachindividual call to unpackNum may fail with a TypeMismatch:

    114 unpackNum : : L i s p V a l > ThrowsError Integer115 unpackNum ( Number n ) = return n116 unpackNum ( String n ) = l e t p a r s ed = reads n in117 i f n ul l p a r s ed118 then throwErr or $ TypeMismatch "

    number" $ String n119 e l s e ret urn $ f s t $ p a rs e d ! ! 0120 unpackNum ( L i s t [ n ] ) = unpackNum n121 unpackNum notNum = throwError $ TypeMismatch "number" notNum

    Finally, we need to change our main function to use this whole big errormonad. This can get a little complicated, because now were dealing with twomonads (Error and IO). As a result, we go back to do-notation, because itsnearly impossible to use point-free style when the result of one monad is nestedinside another:

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    41/138

    33

    7 main : : IO ( )8 main = do

    9 a r g s =

    e v a l11 putStrLn $ e x tr a ct V a lu e $ t r ap E rr o r e v a le d

    Heres what this new function is doing:

    1. args is the list of command-line arguments

    2. evaled is the result of:

    (a) taking first argument (args !! 0)

    (b) parsing it (readExpr)

    (c) passing it to eval (>>=eval; the bind operation has higher precedencethan function application)

    (d) calling show on it within the Errormonad. Note also that the wholeaction has typeIO (Either LispError String), givingevaledtypeEither LispError String. It has to be, because our trapErrorfunction can only convert errors to strings, and that type must matchthe type of normal values

    3. caught is the result of

    (a) callingtrapError on evaled, converting errors to their string repre-sentation.

    (b) callingextractValueto get aStringout of thisEither LispError String

    action(c) printing the results throughputStrLn

    The complete code is therefore:

    Listing 4.1: Lisp parser with basic error-checking functionality (errorcheck.hs)

    module Main whereimpor t Monadimport System .Environmentimport C o n t r o l. Monad.E r r o r

    5 import Text. P a r s e r C o m b i n a t o r s. P a r s e c hiding (s p a c e s)

    main : : IO ( )main = do

    a r g s = e v a l

    putStrLn $ e x tr a c tV a lu e $ t r ap E rr o r e v al e d

    symbol : : P a r s e r Charsymbol = oneOf " ! $ % & | * + -/ : < = ? > @ ^ _ ~ # "

    15

    read E xp r : : String > T h r ow s E r ro r L i s p V a lr e a d Ex p r i n p u t = case p a r s e p a r se E x pr " l i s p " i n p u t of

    Left e r r > th r ow E rr o r $ P a r s er e r rRight v a l > return v a l

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    42/138

    34 CHAPTER 4. ERROR CHECKING AND EXCEPTIONS

    20

    s p a c e s : : P a r s e r ( )s p a c e s = s k ip Ma n y1 s p a c e

    data L i s p V a l = Atom String25 | L i s t [L i s p V a l]

    | D o t t e d L i s t [L i s p V a l] L i s p V a l| Number Integer| S tr i n g S tr i ng| Bool Bool

    30

    p a r s e S t r i n g : : P a r s e r L i s p Va lp a r s e S t r i n g = do c h a r "

    x Atom atom

    45

    parseNumber : : P a r s e r L i s p Va lparseNumber = l i f tM ( Number . read ) $ m any1 d i g i t

    p a r s e L i s t : : P a r s e r L i s p Va l50 p a r s e L i s t = l if t M L i s t $ s e pB y p a r s e E x pr s p a c e s

    p a r s e D o t t e d L i s t : : P a r s e r L i s p Va lp a r s e D o t t e d L i s t = do

    head s p a c e s >> p a r s e E x p r

    return $ D o t t ed L i s t head t a i l

    p arseQu oted : : P a r s e r L i s p Va lp arseQu oted = do

    60 c h a r \

    x Stringu n w o r d s L i s t = unwords . map showVal

    85

    inst ance Show L i s p V a l where show = showVal

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    43/138

    35

    e v a l : : L i s p V a l > T h r ow s E rr o r L i s p V a le v a l v al @ ( String ) = return v a l

    90 e v a l v a l@ ( Number ) = return v a le v a l v al @ ( Bool ) = return v a le v a l ( Li s t [ Atom " q u o t e ", v a l] ) = return v a le v a l ( Li s t ( Atom fun c : a r g s) ) = mapM e va l a r g s >>= a pp l y f u n ceva l b ad Form = t h r o w E r r o r $ B a d Sp e c ia l F o rm " U n r e c o g n i z e d s p e c i al f o r m "

    badForm95

    ap p l y : : String > [L i s p V a l] > T h ro w s E rr o r L i s p V a la p pl y f u nc a r g s = maybe ( t h r o w E r r o r $ N o t F un c t i on " U n r e c o g n i z e d

    p r i mi t i ve f u n ct i o n a r gs " f u n c )( $ a r gs)( lookup f un c p r i m i t i v e s)

    100

    p r i m i t i v e s : : [ ( String , [L i s p V a l] > T h r ow s E rr o r L i s p V a l ) ]p r i m i t i v e s = [ (" + ", n u meri cBi n op (+)) ,

    ( " - ", n u meri cBi n op () ) ,( " * ", n u meri cBi n op () ) ,

    105 ( " / ", n u meri cBi n op di v ) ,( " m o d ", n u meri cBi n op mod) ,

    ( " q u o t i e n t " , n u meri cBi n op quot ) ,( " r e m a i n d e r ", n u meri cBi n op rem) ]

    110 n u meri cBi n op : : ( Integer > Integer > Integer ) > [L i s p V a l] >T h r ow s E r ro r L i s p V a l

    n u m e ri c B i no p o p s i n g l e V a l @ [ ] = th rowE rror $ Nu mA rgs 2 s i n g l e V a ln u m e ri c B i no p o p p ar a ms = mapM unpackNum params >>= return . Number .

    f o l d l 1 op

    unpackNum : : L i s p V a l > Th rowsE rror Integer115 unpackNum ( Number n ) = return n

    unpackNum ( String n ) = l e t p a r s e d = reads n ini f n ul l p a r s e d

    then th rowE rror $ TypeMismatch " n u m b e r " $String n

    e l s e r et ur n $ f s t $ p a r se d ! ! 0120 unpackNum ( L i s t [ n ] ) = unpackNum n

    unpackNum notNum = th rowE rror $ TypeMismatch " n u m b e r " notNum

    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

    125 | P a r s e r P a r s e E r r o r| Bad S p eci al Form String L i s p V a l| N otFu n cti on S tr i ng S tr i ng| UnboundVar S tr i ng S tr i ng| D e f a u l t String

    130

    sh owE rror : : L i s p E r r o r > Stringsh owE rror ( UnboundVar messa ge varname ) = message ++ " : " ++ varnamesh owE rror ( B a d Sp e c ia l F o rm m e s s a g e f o r m ) = message ++ " : " ++ show formsh owE rror ( N o t F u nc t i o n m e s s a g e f u n c ) = message ++ " : " ++ show f u n c

    135 sh owE rror ( Nu mA rgs ex p ecte d fou n d ) = " E x pe c t ed " ++ show e x p e c t e d++ " a r g s : f o u nd v al ue s " ++

    u n w o r d s L i s t f o u n dsh owE rror ( T yp eM is ma tc h e x p e c t e d f o u n d ) = " I nv a l id t y pe : e x pe c t ed " ++

    e x p e c t e d++ ", f ou nd " ++ show fou n d

    sh owE rror (P a r s er p a r s eE r r) = " Pa rs e e rr or a t " ++ show p a r s e E r r

    140

    insta nce Show L i s p E r r o r where show = sh owE rror

    instance E r r o r L i s p Er r o r wherenoMsg = D e f a u l t " A n e r r o r h as o c c ur r e d "

    145 strMsg = D e f a u l t

    type Th rowsE rror = Either L i s p E r r o r

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    44/138

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    45/138

    Chapter 5

    Evaluation, Part 2

    live version discussion

    edit

    comment

    report an error

    Additional Primitives: Partial Application

    [edit section]

    Now that we can deal with type errors, bad arguments, and so on, well fleshout our primitive list so that it does something more than calculate. Well addboolean operators, conditionals, and some basic string operations.

    Start by adding the following into the list of primitives:

    109 ( "=", numBoolBinop (==)) ,110 ( " " , numBoolBinop (>) ) ,112 ( "/=", numBoolBinop (/=)) ,113 ( ">=", numBoolBinop (>=)) ,114 ( "

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    46/138

    38 CHAPTER 5. EVALUATION, PART 2

    130 r i g h t ThrowsError String147 unpackStr ( String s) = return s148 unpackStr ( Number s) = return $ show s

    149 unpackStr ( Bool s) = return $ show s150 u n pa c k St r n o t S t r i n g = throwError $ TypeMismatch "string"

    n o t S t r i n g

    And we use similar code to unpack booleans:

    152 unpackBool : : L i s p V a l > ThrowsError Bool153 unpackBool ( Bool b ) = return b154 u n pa ck Bo o l n o t Bo o l = throwError $ TypeMismatch "boolean"

    notBool

    The complete code is therefore:

    Listing 5.1: A simple parser, now with several primitive operators (operator-parser.hs)

    module Main whereimpor t Monadimport System.Environmentimport C o n t r o l. Monad.E r r o r

    5 import Text. P a r s e r C o m b i n a t o r s. P a r s e c hiding (s p a c e s)

    main : : IO ( )main = do

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    47/138

    ADDITIONAL PRIMITIVES: PARTIAL APPLICATION 39

    a r g s = e v a l

    putStrLn $ e x tr a c tV a lu e $ t r ap E rr o r e v al e d

    symbol : : P a r s e r Charsymbol = oneOf " ! $ % & | * + -/ : < = ? > @ ^ _ ~ # "

    15

    read E xp r : : String > T h r ow s E r ro r L i s p V a lr e a d Ex p r i n p u t = case p a r s e p a r se E x pr " l i s p " i n p u t of

    Left e r r > th r ow E rr o r $ P a r s er e r rRight v a l > return v a l

    20

    s p a c e s : : P a r s e r ( )s p a c e s = s k ip M an y 1 s p a c e

    data L i s p V a l = Atom String25 | Li s t [L i s p V a l]

    | D o t t e d L i s t [L i s p V a l] L i s p V a l| Number Integer| S tr i n g S tr i n g| Bool Bool

    30

    p a r s e S t r i n g : : P a r s e r L i s p Va lp a r s e S t r i n g = do c h a r "

    x Atom atom

    45

    parseNumber : : P a r s e r L i s p Va lparseNumber = l i f tM ( Number . read ) $ m any1 d i g i t

    p a r s e L i s t : : P a r s e r L i s p Va l

    50 p a r s e L i s t = l if t M L i s t $ s ep By p a r s eE x pr s p a c e s

    p a r s e D o t t e d L i s t : : P a r s e r L i s p Va lp a r s e D o t t e d L i s t = do

    head s p a c e s >> p a r s e E x p r

    return $ D o t t ed L i s t he ad t a i l

    p arseQu oted : : P a r s e r L i s p Va lp arseQu oted = do

    60 c h a r \ x

  • 8/13/2019 Write Yourself a Scheme in 48 Hours

    48/138

    40 CHAPTER 5. EVALUATION, PART 2

    showVal ( N umber co n te n ts ) = show c o n t e n t sshowVal ( Bool True) = " # t "showVal ( Bool False ) = " # f "

    80 showVal ( Li s t c o n t e n t s) = " ( " ++ un w o r ds L i s t c o n t e n t s ++ " ) "showVal (D o t t e d L i s t he ad t a i l ) = " ( " ++ u n w o r d s L i s t head ++ " . " ++

    showVal t a i l ++ " ) "

    u n w o r d s L i s t : : [L i s p V a l] > Stringu n w o r d s L i s t = unwords . map showVal

    85

    inst ance Show L i s p V a l where show = showVal

    e v a l : : L i s p V a l > T h ro w s E rr o r L i s p V a le v a l v al @ ( String ) = return v a l