15
分享两道有意思的 Haskell 习题(可以看看我的中文注解),其中我写的答案很垃圾,所以我就不放上我的答案了。我这里用的是官方答案,写得很好,让我得以自我反思和自我批评、自我更正:
------------------------------------------------------------------------------
-- Ex 8: Improved phone number validation. Implement the function
-- normalizePhone that, given a String:
--
-- * removes all spaces from the string
-- * checks that there are at most 10 remaining characters
-- * checks that all remaining characters are digits, and logs an
-- error for every nonvalid character
-- * returns the string, stripped of whitespace, if no errors
--
-- Examples:
-- normalizePhone "123 456 78" ==> Ok "12345678"
-- normalizePhone "123 4x6 78"
-- ==> Errors ["Invalid character: x"]
-- normalizePhone "123 4x6 7y"
-- ==> Errors ["Invalid character: x","Invalid character: y"]
-- normalizePhone "123 4x6 7y 999"
-- ==> Errors ["Too long","Invalid character: x","Invalid character: y"]
-- normalizePhone "123 456 78 999"
-- ==> Errors ["Too long"]
-- (<$>) 和 (<*>) 函数用于一串的确定数目变量构造
-- traverse 用于一串不确定数目变量(储存在list中)构造
-- (*>) , (<*) 用于有一些虽然输出结果,但是我们只用于判断是否 valid,而不用它的结果的情况(如本题)
-- 另外,如下题所示,有时候需要配合上 pattern matching 使用。
-- 也就是,先 tokenize,再自底而上地 parse
-- 兼用
-- (<$>) 和 (<*>)(与,全利用)
-- (<*), (*>)(与,只利用左边的/右边的)
-- 和 (<|>)(或,只利用左边的**或者**右边的)
-- 三种
-- 并配合上 pattern matching,实现对不同情况,不同 parse 地精确掌握
normalizePhone :: String -> Validation String
normalizePhone xs = checkLength trimmedStr *> traverse validateChar trimmedStr
where
trimmedStr = filter (not . isSpace) xs
checkLength :: String -> Validation String
checkLength xs = check (length xs <= 10) "Too long" xs
validateChar :: Char -> Validation Char
validateChar c = check (isDigit c) ("Invalid character: " ++ [c]) c
------------------------------------------------------------------------------
-- Ex 9: Parsing expressions. The Expression type describes an
-- arithmetic expression that has an operator (+ or -) and two
-- arguments that can be either numbers or single-letter variables.
-- The operator and the arguments are always separated by spaces. Here
-- are some examples of expressions like this: 1 + 2, y + 7, z - w
--
-- Implement the function parseExpression that uses the Validation
-- applicative to convert strings like "y + 7" to Expression values
-- like Plus (Variable 'y') (Number 7).
--
-- The parser should produce the following errors:
-- * For operators other than + or -: "Unknown operator: %"
-- * For variables that aren't single letters: "Invalid variable: xy"
-- * For arguments that aren't numbers: "Invalid number: 1x" --
-- * For expressions that don't consist of three words:
-- "Invalid expression: 1 + 2 +"
-- "Invalid expression: 1 -"
--
-- Hint: The functions `words` and `isAlpha`
--
-- Hint: If you have problems with the ordering of errors, remember
-- that Validation collects errors left-to-right!
--
-- Examples:
-- parseExpression "1 + 2" ==> Ok (Plus (Number 1) (Number 2))
-- parseExpression "z - A" ==> Ok (Minus (Variable 'z') (Variable 'A'))
-- parseExpression "1 * 2" ==> Errors ["Unknown operator: *"]
-- parseExpression "1 + 2x"
-- ==> Errors ["Invalid number: 2x","Invalid variable: 2x"]
-- parseExpression ". % 2x"
-- ==> Errors ["Unknown operator: %",
-- "Invalid number: .","Invalid variable: .",
-- "Invalid number: 2x","Invalid variable: 2x"]
-- 如本题所示,有时候需要配合上 pattern matching 使用。
-- 也就是,先 tokenize,再自底而上地 parse
-- 兼用
-- (<$>) 和 (<*>)(与,全利用)
-- (<*), (*>)(与,只利用左边的/右边的)
-- 和 (<|>)(或,只利用左边的**或者**右边的)
-- 三种
-- 并配合上 pattern matching,实现对不同情况,不同 parse 地精确掌握
data Arg = Number Int | Variable Char
deriving (Show, Eq)
data Expression = Plus Arg Arg | Minus Arg Arg
deriving (Show, Eq)
parseExpression :: String -> Validation Expression
parseExpression ss = parseTokens (words ss)
where
parseTokens [left, op, right] = parseOperator op <*> parseOperand left <*> parseOperand right
parseTokens s = invalid $ "Invalid expression: " ++ ss
parseVariable :: String -> Validation Arg
parseVariable [c]
| isAlpha c = pure (Variable c)
parseVariable s = invalid $ "Invalid variable: " ++ s
parseNumber :: String -> Validation Arg
parseNumber s
| all isDigit s = pure (Number (read s))
parseNumber s = invalid $ "Invalid number: " ++ s
parseOperand :: String -> Validation Arg
parseOperand s = parseNumber s <|> parseVariable s
parseOperator :: String -> Validation (Arg -> Arg -> Expression)
parseOperator "+" = pure Plus
parseOperator "-" = pure Minus
parseOperator s = invalid $ "Unknown operator: " ++ s