Re: Покритикуйте калькулятор на Haskell
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 20.11.07 12:03
Оценка: 32 (3)
Здравствуйте, Кодёнок, Вы писали:

Кё>Задача: интерактивный (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>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.