Write Yourself a Scheme in 48 Hours

1. First Steps

module Main where
    import System.Environment

    main :: IO ()
    main = do
        args <- getArgs
        putStrLn $ "Hello," ++ args !! 0
  • () unit type

    • 这种类型只有一个值就是()

  • IO类型是Monad的一个实例

    • 创建一个IO Action

      • Lift一个普通值到IO monad

      • 组合两个已有的IO Action,形成一个新的

        • do block

          • 不能在同一个do-block中混用不同的monad类型

  • Monad是一个concept

    • 有额外信息贴在这个值上

      • Monadic value

        • IO ()中的()就是

    • 大部分函数不用关注这些额外信息

exercises:

    main :: IO ()
    main = do
        args <- getArgs
        putStrLn $ "Hello," ++ args !! 0 ++ "," ++ args !! 1
        putStrLn $ show $ (read (args !! 0)) * (read (args !! 1))
        line <- getLine
        putStrLn $ "Hello," ++ line

2. Parsing

2.1 Writing a Simple Parser

    import Text.ParserCombinators.Parsec 
    import System.Environment

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

    _spaces :: Parser ()
    _spaces = skipMany1 space

    readExpr :: String -> String
    readExpr input = case parse (_spaces >> symbol) "lisp" input of 
        Left err -> "No match:" ++ show err
        Right val -> "Found value:" ++ show val

    main :: IO ()
    main = do
        (expr:_) <- getArgs
        putStrLn $ readExpr expr
  • Parsec

    • cabal install parsec

  • Parser

    • symbol定义

      • 一个monad,其中额外信息有

        • input stream中的位置

        • backtracking记录

        • fisrt&follow集

    • parse

      • parse :: Stream s Identity t => Parsec s () a -> SourceName -> s -> Either ParseError a

  • Either类型

    • data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)

  • case ... of 模式匹配

2.2 Whitespace

    _spaces :: Parser ()
    _spaces = skipMany1 space

    readExpr :: String -> String
    readExpr input = case parse (_spaces >> symbol) "lisp" input of 
        Left err -> "No match:" ++ show err
        Right val -> "Found value:" ++ show val

增加对空字符的识别跳过.

  • >> bind(不带值)

    • Attempt to match the first parser, then attempt to match the second with the remaining input, and fail if either fails.

2.3 Return values

    data LispVal = Atom String
        | List [LispVal]
        | DottedList [LispVal] LispVal
        | Number Integer
        | String String
        | Bool Bool

    parseString :: Parser LispVal
    parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return $ String x

    parseAtom :: Parser LispVal
    parseAtom = do
        first <- letter <|> symbol
        rest <- many (letter <|> digit <|> symbol)
        let atom = first: rest
        return $ case atom of 
            "#t" -> Bool True
            "#f" -> Bool False
            _ -> Atom atom
    
    parseNumber :: Parser LispVal
    parseNumber = liftM (Number . read) $ many1 digit
    
    parseExpr :: Parser LispVal
    parseExpr = parseAtom
        <|> parseString
        <|> parseNumber
  • 抽象数据类型

  • parseString

    • $ 改变结合顺序

    • return lift前面many拿到的值进入到Parser monad中

  • parseAtom

    • <|>, choice operator(一个combinator)

      • 尝试第一个parser,如果失败尝试下一个

        • 一个parser一定是在它消费输入前失败掉, 但是它需要看输入才能判断是否失败, 因此需要backtrack.

    • :, list cons operator

  • parseNumber

    • 从右向左读,函数都是右结合的

    • many1 digit返回的是Parser String, 没有办法只是应用(Number . read)

    • liftM将函数应用到monad中的值里,