在 48 小时内编写自己的 Scheme/答案
| ←结论 | 在 48 小时内编写自己的 Scheme 答案 |
本教程的读者贡献了解决方案。认为您有答案? 贡献! |
main :: IO ()
main = do args <- getArgs
putStrLn ("Hello, " ++ args!!0 ++ " " ++ args!!1)
main :: IO ()
main = do args <- getArgs
print ((read $ args!!0) + (read $ args!!1))
$ 运算符减少了此处所需的括号数量。或者,您可以将函数应用编写为 read (args!!0)。
main :: IO ()
main = do putStrLn "What do they call thee at home?"
name <- getLine
putStrLn ("Ey up " ++ name)
parseNumber :: Parser LispVal
parseNumber = do x <- many1 digit
(return . Number . read) x
为了回答这个问题,您需要进行一些侦探工作! 阅读有关 do 符号 的内容会很有帮助。 使用那里的信息,我们可以机械地将上述答案转换为以下内容。
parseNumber = many1 digit >>= \x -> (return . Number . read) x
这可以清理为以下内容
parseNumber = many1 digit >>= return . Number . read
我们需要创建一个新的解析器操作,该操作接受一个反斜杠,后跟另一个反斜杠或双引号。 此操作需要只返回第二个字符。
escapedChars :: Parser Char
escapedChars = do char '\\' -- a backslash
x <- oneOf "\\\"" -- either backslash or doublequote
return x -- return the escaped character
完成后,我们需要对 parseString 进行一些更改。
parseString :: Parser LispVal
parseString = do char '"'
x <- many $ escapedChars <|> noneOf "\"\\"
char '"'
return $ String x
escapedChars :: Parser Char
escapedChars = do char '\\'
x <- oneOf "\\\"nrt"
return $ case x of
'\\' -> x
'"' -> x
'n' -> '\n'
'r' -> '\r'
't' -> '\t'
首先,有必要更改 symbol 的定义。
symbol :: Parser Char symbol = oneOf "!$%&|*+-/:<=>?@^_~"
这意味着原子不再可以用哈希字符开头。 这需要一种不同的解析 #t 和 #f 的方法。
parseBool :: Parser LispVal
parseBool = do
char '#'
(char 't' >> return (Bool True)) <|> (char 'f' >> return (Bool False))
反过来,这要求我们对 parseExpr 进行更改。
parseExpr :: Parser LispVal
parseExpr = parseAtom
<|> parseString
<|> parseNumber
<|> parseBool
parseNumber 需要更改为以下内容。
parseNumber :: Parser LispVal parseNumber = parseDecimal1 <|> parseDecimal2 <|> parseHex <|> parseOct <|> parseBin
并且需要添加以下新函数。
parseDecimal1 :: Parser LispVal parseDecimal1 = many1 digit >>= (return . Number . read)
parseDecimal2 :: Parser LispVal
parseDecimal2 = do try $ string "#d"
x <- many1 digit
(return . Number . read) x
parseHex :: Parser LispVal
parseHex = do try $ string "#x"
x <- many1 hexDigit
return $ Number (hex2dig x)
parseOct :: Parser LispVal
parseOct = do try $ string "#o"
x <- many1 octDigit
return $ Number (oct2dig x)
parseBin :: Parser LispVal
parseBin = do try $ string "#b"
x <- many1 (oneOf "10")
return $ Number (bin2dig x)
oct2dig x = fst $ readOct x !! 0
hex2dig x = fst $ readHex x !! 0
bin2dig = bin2dig' 0
bin2dig' digint "" = digint
bin2dig' digint (x:xs) = let old = 2 * digint + (if x == '0' then 0 else 1) in
bin2dig' old xs
导入 Numeric 模块以使用 readOct 和 readHex 函数。
data LispVal = Atom String
| List [LispVal]
| DottedList [LispVal] LispVal
| Number Integer
| String String
| Bool Bool
| Character Char
parseCharacter :: Parser LispVal
parseCharacter = do
try $ string "#\\"
value <- try (string "newline" <|> string "space")
<|> do { x <- anyChar; notFollowedBy alphaNum ; return [x] }
return $ Character $ case value of
"space" -> ' '
"newline" -> '\n'
otherwise -> (value !! 0)
anyChar 和 notFollowedBy 的组合确保只读取单个字符。
请注意,这实际上不符合标准; 按照目前的做法,“空格”和“换行符”必须完全是小写; 标准指出它们应该不区分大小写。
parseExpr :: Parser LispVal
parseExpr = parseAtom
<|> parseString
<|> try parseNumber -- we need the 'try' because
<|> try parseBool -- these can all start with the hash char
<|> try parseCharacter
浮点数的可能解决方案
parseFloat :: Parser LispVal
parseFloat = do x <- many1 digit
char '.'
y <- many1 digit
return $ Float (fst.head$readFloat (x++"."++y))
此外,添加
try parseFloat
在 parseExpr 中的 parseNumber 之前,以及以下行
| Float Double
到 LispVal 类型。
Ratio,使用 Haskell 的 Rational 类型
parseRatio :: Parser LispVal
parseRatio = do x <- many1 digit
char '/'
y <- many1 digit
return $ Ratio ((read x) % (read y))
此外,导入 Data.Ratio 模块,添加
try parseRatio
在 parseExpr 中的 parseNumber 之前,以及以下行
| Ratio Rational
到 LispVal 类型。
Real 已经用 Exercise 6 中的 Float 类型实现,除非我错了。
Complex,使用 Haskell 的 Complex 类型
toDouble :: LispVal -> Double toDouble(Float f) = realToFrac f toDouble(Number n) = fromIntegral n
parseComplex :: Parser LispVal
parseComplex = do x <- (try parseFloat <|> parseDecimal)
char '+'
y <- (try parseFloat <|> parseDecimal)
char 'i'
return $ Complex (toDouble x :+ toDouble y)
与之前一样,导入 Data.Complex 模块,添加
try parseComplex
在 parseExpr 中的 parseNumber 和 parseFloat 之前,以及以下行
| Complex (Complex Double)
到 LispVal 类型。
这两个类似于 parseQuoted
parseQuasiQuoted :: Parser LispVal
parseQuasiQuoted = do
char '`'
x <- parseExpr
return $ List [Atom "quasiquote", x]
parseUnQuote :: Parser LispVal
parseUnQuote = do
char ','
x <- parseExpr
return $ List [Atom "unquote", x]
parseUnQuoteSplicing :: Parser LispVal
parseUnQuoteSplicing = do
char ','
char '@'
x <- parseExpr
return $ List [Atom "unquote-splicing", x]
还添加
<|> parseQuasiQuoted
<|> parseUnQuote
<|> parseUnQuoteSplicing
到 parseExpr。
我选择使用 Data.Array 中描述的数组,并使用列表-数组转换来构建数组。
parseVector :: Parser LispVal
parseVector = do arrayValues <- sepBy parseExpr spaces
return $ Vector (listArray (0,(length arrayValues - 1)) arrayValues)
为了使用它,导入 Data.Array 并将以下内容添加到 LispVal 类型中
| Vector (Array Int LispVal)
将以下行添加到 parseExpr 中;在列表和点式列表的解析器之前。
<|> try (do string "#("
x <- parseVector
char ')'
return x)
练习 3
[edit | edit source]这需要花一些时间调整 sepBy、endBy 及其朋友。我从让 (. degenerate) 点式列表工作开始,然后从那里开始。这段代码可以容忍尾部和前导空格。
parseAnyList :: Parser LispVal
parseAnyList = do
P.char '('
optionalSpaces
head <- P.sepEndBy parseExpr spaces
tail <- (P.char '.' >> spaces >> parseExpr) <|> return (Nil ())
optionalSpaces
P.char ')'
return $ case tail of
(Nil ()) -> List head
otherwise -> DottedList head tail
另一个使用 Parsec 库中更高级函数的实现。spaces 是本教程中的一个。
parseList :: Parser LispVal
parseList = between beg end parseList1
where beg = (char '(' >> skipMany space)
end = (skipMany space >> char ')')
parseList1 :: Parser LispVal
parseList1 = do list <- sepEndBy parseExpr spaces
maybeDatum <- optionMaybe (char '.' >> spaces >> parseExpr)
return $ case maybeDatum of
Nothing -> List list
Just datum -> DottedList list datum
另一种解决方案。spaces 是来自 Parsec 的空格,spaces1 是本教程中的空格。
parseList :: Parser LispVal
parseList = do char '(' >> spaces
head <- parseExpr `sepEndBy` spaces1
do char '.' >> spaces1
tail <- parseExpr
spaces >> char ')'
return $ DottedList head tail
<|> (spaces >> char ')' >> (return $ List head))
第三章
[edit | edit source]练习 1
[edit | edit source]以下是一种添加其中一些的方法。
primitives :: [(String , [LispVal] -> LispVal)]
primitives = [("+" , numericBinop (+)) ,
("-" , numericBinop (-)) ,
("*" , numericBinop (*)) ,
("/" , numericBinop div) ,
("mod" , numericBinop mod) ,
("quotient" , numericBinop quot) ,
("remainder" , numericBinop rem) ,
("symbol?" , unaryOp symbolp) ,
("string?" , unaryOp stringp) ,
("number?" , unaryOp numberp) ,
("bool?", unaryOp boolp) ,
("list?" , unaryOp listp)]
unaryOp :: (LispVal -> LispVal) -> [LispVal] -> LispVal unaryOp f [v] = f v
symbolp, numberp, stringp, boolp, listp :: LispVal -> LispVal symbolp (Atom _) = Bool True symbolp _ = Bool False numberp (Number _) = Bool True numberp _ = Bool False stringp (String _) = Bool True stringp _ = Bool False boolp (Bool _) = Bool True boolp _ = Bool False listp (List _) = Bool True listp (DottedList _ _) = Bool False listp _ = Bool False
练习 2
[edit | edit source]unpackNum :: LispVal -> Integer unpackNum (Number n) = n unpackNum _ = 0
练习 3
[edit | edit source]将 symbol->string 和 string->symbol 添加到基本函数列表中,然后
symbol2string, string2symbol :: LispVal -> LispVal symbol2string (Atom s) = String s symbol2string _ = String "" string2symbol (String s) = Atom s string2symbol _ = Atom ""
这不能很好地处理错误的输入,这将在后面讨论。
第五章
[edit | edit source]练习 1
[edit | edit source]eval env (List [Atom "if", pred, conseq, alt]) = do
result <- eval env pred
case result of
Bool False -> eval env alt
Bool True -> eval env conseq
_ -> throwError $ TypeMismatch "bool" pred
练习 2
[edit | edit source]定义一个辅助函数,它将相等/等价函数作为参数。
eqvList :: ([LispVal] -> ThrowsError LispVal) -> [LispVal] -> ThrowsError LispVal
eqvList eqvFunc [(List arg1), (List arg2)] = return $ Bool $ (length arg1 == length arg2) &&
(all eqvPair $ zip arg1 arg2)
where eqvPair (x1, x2) = case eqvFunc [x1, x2] of
Left err -> False
Right (Bool val) -> val
现在调整 eqv 子句
eqv listPair@[List _, List _] = eqvList eqv listPair
并将列表和点式列表的子句添加到相等函数中
equal :: [LispVal] -> ThrowsError LispVal
equal listPair@[List _, List _] = eqvList equal listPair
equal [(DottedList xs x), (DottedList ys y)] = equal [List $ xs ++ [x], List $ ys ++ [y]]
equal [arg1, arg2] = do
primitiveEquals <- liftM or $ mapM (unpackEquals arg1 arg2)
[AnyUnpacker unpackNum, AnyUnpacker unpackStr, AnyUnpacker unpackBool]
eqvEquals <- eqv [arg1, arg2]
return $ Bool $ (primitiveEquals || let (Bool x) = eqvEquals in x)
equal badArgList = throwError $ NumArgs 2 badArgList
练习 3
[edit | edit source]cond
[edit | edit source]这里还有改进的空间!
eval (List ((Atom "cond"):cs)) = do
b <- (liftM (take 1 . dropWhile f) $ mapM condClause cs) >>= cdr
car [b] >>= eval
where condClause (List [p,b]) = do q <- eval p
case q of
Bool _ -> return $ List [q,b]
_ -> throwError $ TypeMismatch "bool" q
condClause v = throwError $ TypeMismatch "(pred body)" v
f = \(List [p,b]) -> case p of
(Bool False) -> True
_ -> False
另一种方法
eval env (List (Atom "cond" : expr : rest)) = do
eval' expr rest
where eval' (List [cond, value]) (x : xs) = do
result <- eval env cond
case result of
Bool False -> eval' x xs
Bool True -> eval env value
otherwise -> throwError $ TypeMismatch "boolean" cond
eval' (List [Atom "else", value]) [] = do
eval env value
eval' (List [cond, value]) [] = do
result <- eval env cond
case result of
Bool True -> eval env value
otherwise -> throwError $ TypeMismatch "boolean" cond
另一种方法,利用已经实现的 if 函数。
eval form@(List (Atom "cond" : clauses)) =
if null clauses
then throwError $ BadSpecialForm "no true clause in cond expression: " form
else case head clauses of
List [Atom "else", expr] -> eval expr
List [test, expr] -> eval $ List [Atom "if",
test,
expr,
List (Atom "cond" : tail clauses)]
_ -> throwError $ BadSpecialForm "ill-formed cond expression: " form
另一种方法
eval (List ((Atom "cond") : alts)) = cond alts
cond :: [LispVal] -> ThrowsError LispVal
cond ((List (Atom "else" : value : [])) : []) = eval value
cond ((List (condition : value : [])) : alts) = do
result <- eval condition
boolResult :: Bool <- unpackBool result
if boolResult then eval value
else cond alts
cond ((List a) : _) = throwError $ NumArgs 2 a
cond (a : _) = throwError $ NumArgs 2 [a]
cond _ = throwError $ Default "Not viable alternative in cond"
case
[edit | edit source]此解决方案需要 LispVal 具有 deriving (Eq) 子句,以便使用 `elem` 函数。
eval form@(List (Atom "case" : key : clauses)) =
if null clauses
then throwError $ BadSpecialForm "no true clause in case expression: " form
else case head clauses of
List (Atom "else" : exprs) -> mapM eval exprs >>= return . last
List ((List datums) : exprs) -> do
result <- eval key
equality <- mapM (\x -> eqv [result, x]) datums
if Boolean True `elem` equality
then mapM eval exprs >>= return . last
else eval $ List (Atom "case" : key : tail clauses)
_ -> throwError $ BadSpecialForm "ill-formed case expression: " form
练习 4
[edit | edit source]让我们添加 string-length 和 string-ref
primitives = [...
("string-length", stringLen), │
("string-ref", stringRef),
...]
stringLen :: [LispVal] -> ThrowsError LispVal stringLen [(String s)] = Right $ Number $ fromIntegral $ length s stringLen [notString] = throwError $ TypeMismatch "string" notString stringLen badArgList = throwError $ NumArgs 1 badArgList
stringRef :: [LispVal] -> ThrowsError LispVal
stringRef [(String s), (Number k)]
| length s < k' + 1 = throwError $ Default "Out of bound error"
| otherwise = Right $ String $ [s !! k']
where k' = fromIntegral k
stringRef [(String s), notNum] = throwError $ TypeMismatch "number" notNum
stringRef [notString, _] = throwError $ TypeMismatch "string" notString
stringRef badArgList = throwError $ NumArgs 2 badArgList