HaskellでSchemeを書く#4
追記:見直すこと!!
ここらで一度、ソースを整理することにした。 ついでにためてた宿題も片付けることに。
まずは、構文解析の章。
1.parseNumberの書き換え
- 1.do記法
parseNumber :: Parser LispVal parseNumber = do x <- many1 digit return $ (Number . read) x
*2.>>=
による明示的シーケンシング
parseNumber :: Parser LispVal parseNumber = many1 digit >>= return . Number . read
2は解答を見てしまった。>>= (return (Number . read))
とかいていて、なんで通らないんだ・・と思ってたけども、合成しないといけなかったのか。未だ不慣れ・・・
*Main> :t (return (Number . read)) (return (Number . read)) :: Monad m => m (String -> LispVal) *Main> :t return . Number .read return . Number .read :: Monad m => String -> m LispVal
2.parseStringの終端判定修正
3.escape文字の実装
これは問題の理解に手間取った・・。\\\"
とか、\\n
のようなものを実装すればいいんだな。
なのでとりあえず一度\\
でパースして続く文字を取り出してあげれば、\"
が単体で出てきたときに終端文字として正しく認識される、ということかな。
parseString
の中でパースするにあたり、Parser Char
で返さなきゃ行けなくて、エスケープ文字を抽出したあと一文字で返す方法がわからず(文字列として連結して渡そうとしてた)、結局かなり筋が悪い感じになった
parseString :: Parser LispVal parseString = do char '"' x <- many (parseEscape <|> noneOf "\"") char '"' return $ String x parseEscape :: Parser Char parseEscape = do char '\\' x <- oneOf "\\nrts" return $ case x of 'n' -> '\n' 'r' -> '\r' 't' -> '\t' _ -> x
*Main> parseTest parseString "\"abc\\\"abc\\\\1\\n3\"" "abc"abc\1 3"
直したい・・・。
4.'#'prefixの実装
The radix prefixes are #b (binary), #o (octal), #d (decimal), and #x (hexadecimal). With no radix prefix, a number is assumed to be expressed in decimal.
parseNumberよりは、parseAtomを直すのが良いように思える・・・。どうせなので、prefixParserを作る。#
prefixを別で処理することにしたので、symbol
に吸い込まれないように修正。
symbol :: Parser Char symbol = oneOf "!$%&|*+-/:<=>?@^_~" parseAtom :: Parser LispVal parseAtom = do first <- letter <|> symbol rest <- many (letter <|> digit <|> symbol) let atom = first:rest return $ case atom of _ -> Atom atom parsePrefix :: Parser LispVal parsePrefix = do char '#' prefix <- letter <|> symbol expression <- many(letter <|> digit) return $ case prefix of 'x' -> Number $ fst $ (readHex expression) !! 0 'd' -> (Number . read) expression 'o' -> Number $ fst $ (readOct expression) !! 0 --'b' -> readBin 't' -> Bool True 'f' -> Bool False
parseAtomの意味がなくなってしまったな・・・。本当は、first
で処理を分けたかったけど、その場合だと、
一度文字列を処理してparsePrefix
でparseするという関数をもう1つ作らなければならなかったのでそちらのほうが筋悪と考えた。readBin
はNumeric
モジュールになかったのでまた考える。
*Main Numeric> parseTest parsePrefix "#d1234" 1234 *Main Numeric> parseTest parsePrefix "#x1234" 4660
5.Characterリテラルの実装
Characters are objects that represent printed characters such as letters and digits. Characters are written using the notation #\\
or #\\ . For example:
- #\a ; lower case letter
- #\A ; upper case letter
- #\( ; left parenthesis
- #\ ; the space character
- #\space ; the preferred way to write a space
- #\newline ; the newline character
#\
から始まる表現みたい。何もLispVal
に加えるほどのものでも無い気がするけど、どうなんだろう。
後続の文字が1文字ならそのまま返して、space
or newline
なら変換して返す感じかなぁ。
一文字だったら、文字列に一致したら、っていう条件分岐の書き方がわからない・・、のでカンニング・・・
value <- try (string "newline" <|> string "space") <|> do { x <- anyChar; notFollowedBy alphaNum ; return [x] }
alphaNum
は letter
とdigit
をあわせたパーサで、notFollowedBy
は、何かと組み合わせて、指定されたパーサでパースできるものが続いてる場合はError、そうじゃなければ通す、みたいな関数らしい。(参考Parsec notFollowedBy - tnomuraのブログ)
do
のこういうくくり方全然思いつけないなぁ・・。写経することにする。ついでにShowValも変更。
6.Floatコンストラクタの追加
これは、これまでのに比べると簡単。
parseFloat :: Parser LispVal parseFloat = do head <- many1 digit char '.' tail <- many1 digit return $ Float $ fst $ (readFloat $ head ++ "." ++ tail) !! 0
parseExpr
にはtry
を入れて、parseNumber
の前に追加
*Main Numeric Text.ParserCombinators.Parsec> parseTest parseExpr "(12.3 123)" (12.3 123)
7.数値表現の追加
Mathematically, numbers may be arranged into a tower of subtypes in which each level is a subset of the level above it:
number
- complex
- real
- rational
- integer
real
とinteger
は実装済なので、rational
とcomplex
の実装。まずは、rational
、有理数・・。
4/6 => 2/3 3/12 => 1/4 10/5 => 2 ; 整数に変換される
こんな感じになるらしい。なんと、HaskellにはRational
型があった。凄い。10 % 11
のように表示されるということだけど、少数に直したりすると正確な値とはずれるらしい。(参考:すごい乱暴な方法でHaskellの少数を正確数にする-Qiita)
念頭におきつつ、多分今回は関係ないので、とりあえず例にあるような形の数字を、Rational型にする。
parseRational :: Parser LispVal parseRational = do denom <- many1 digit char '/' num <- many1 digit return $ Rational $ read(denom) % read(num)
そのまんまな感じ。
複素数も、Data.Complex
なるモジュールにあったので、そのまま流用。
Schemeでの複素数の例
5-3i => 5.0-3.0i 1.2+2.4i => 1.2+2.4i 1/2+1/4i => 0.5+0.25i
parseComplex :: Parser LispVal parseComplex = do re <- many1 digit mark <- char '+' <|> char '-' im <- many1 digit char 'i' return $ Complex $case mark of '+' -> read(re) :+ read(im) '-' -> read(re) :+ (- read(im))
勢いに任せてこんな感じで実装し、答え合わせをすると、実数で表示されてるのを完全に忘れてた。確かにSchemeでも実数表示になってる・・。書き直し・・。
parseComplex :: Parser LispVal parseComplex = do re <- (try parseFloat <|> do { x <- many1 digit;return $ Float $ fst $ head $ readFloat(x)}) mark <- char '+' <|> char '-' im <- (try parseFloat <|> do { x <- many1 digit;return $ Float $ fst $ head $ readFloat(x)}) char 'i' return $ Complex $case mark of '+' -> toDouble(re) :+ toDouble(im) '-' -> toDouble(re) :+ ( - toDouble(im))
結局toDouble
を実装してこのように。do
記法でやってみたけど、これは見た目があれだなぁ・・
かなり急いでやってしまったのでまた見直す事。 現在の進捗:github