Здравствуйте, Кодёнок, Вы писали:
Кё>Задача: интерактивный (read-eval loop) калькулятор с поддержкой присваивания переменным, приоритетами и ассоциативностью. Парсер принципиально с нуля.
Неиспользование стандартных библиотек — важно?
Кё>Что можно улучшить?
Обычно, когда видишь функции вида s->(a,s) лучше всего завернуть их в состояние. Код будет гораздо чище. Т.к. у тебя используется IO, то это будет трансформер состояния над IO:
type Env = Map String Float
type Result = String
type CalcS a = StateT Env IO a
readEvalLoop :: CalcS Result
eval :: String -> CalcS Result
calculate :: [Token] -> CalcS Float
reduce :: [Token] -> [Integer] -> CalcS (Float, [Token])
В этом случае код вида
let (x,newenv) = foo z env in (bar x, newenv)
превращается в
x <- foo z
return (bar x)
или, что то же самое
liftM bar (foo z)
Например, у тебя это можно найти в eval и reduce. Во-вторых, но это вопрос стиля, я пытаюсь писать более декларативный код, нежели expression-код. Поэтому код вида
foo = if blah then branchT else branchE
bar = case foobar of { pattern -> expr }
у меня выглядит как
foo | blah = branchT
| otherwise = branchE
bar = expr
where
pattern = foobar
Мне кажется это гораздо выразительнее. Но, повторю, это скорее дело пристрастий.
Вот пример (сравни со своим eval и calculate):
eval :: String -> CalcS Result
eval s
| null tokens = return ""
| otherwise = liftM show (calculate tokens)
where
tokens = tokenize s
calculate :: [Token] -> CalcS Float
calculate [Number x] = return x
calculate ts = liftM fst (reduce ts [0])
В третьих, по моему использование стандартных библиотек — это плюс. Всё таки работа программиста заключается в удалении дублирования
Поэтому, парсинг чисел, идентификаторов, определение приоритетов, выполнение операция лучше возложить на плечи того кода, который для этого предназначен.
ident = many1 alphaNum
number = do n <- integer
f <- fraction
return (fromInteger n + f)
integer = liftM toNum (many1 digit)
where
toNum = foldl (\x d -> 10*x + toInteger (digitToInt d)) 0
fraction = option 0.0 $ try $ do
char '.'
liftM toFrac (many1 digit)
where
toFrac = foldr (\x d -> (x + fromIntegral (digitToInt d))/10.0) 0
или даже
parser = makeTokenParser haskellStyle
ident = identifier parser
number = float parser
string = stringLiteral parser
И для приоритетов
buildExpressionParser
[[op "*" (*) AssocLeft], [op "/" (/) AssocLeft],
[op "+" (+) AssocLeft], [op "-" (-) AssocLeft]
where
op s f assoc = Infix (do{ string s; return f}) assoc
Здесь про парсек:
http://legacy.cs.uu.nl/daan/download/parsec/parsec.html
С parsec-ом и StateT IO должно получиться кратко и красиво. Да там по моему уже и StateT не понадобится
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>