Каква е целта? Получваме низ и връщаме резултат. Резултатът няма да е случаен, а ще е следствие на "изчислението" или "оценката" на низа. Низът от своя страна има структура и следва някакви правила. Работата на интерпретатора грубо може да разделим на два големи етапа - откриването на тази структура в самия низ -parsing, и изпълнението/oценяването - execution/evaluation. Ще се фокусираме само върху втората операция.
Да разгледаме най-простия израз "2+22"
. Kак може да го обработим? След като сме го parsе-нали, сме получили готова структура. Каква може да бъде тази структура? Един вариант е например да са два списъка numbers :: [Int]
и operations::[Char]
. Да видим дали не може да се справим по-добре с ADT
:
data Expression = Add Int Int
Операцията +
я представяме с конструктура Add
. Функцията, която оценява израза, ще изглежда по-следния начин:
eval :: Expresion -> Int
eval (Add x y) = x + y
Сега вече може да сметнем 2+22
, но може ли да смятаме произволни изрази 2+3+4
, (2+2)+2+2
. Ако разгледаме по-внимателно (1+2) + (3+4)
, може да видим, че е съставен от рекурсивни подизрази и да променим Expression
да отразява това:
data Expression = AddI Int Int | AddE Expression Expression
А еval
става:
еval (AddI x y) = x + y
eval (AddE ex ey) = eval ex + eval ey
Сега вече може да смятаме неща като 1+2
, (1+2)+(2+1)
, но все още не `1 + (2+2)``. Лесно може да решим проблем с
data Expression = AddII Int Int
| AddEE Expression Expression
| AddIE Int Expression
| AddEI Expression Int
Всъщност туко-що написахме всички възможни комбинации за две елемента между Expression
и Int
. Може ли да го съкратим? След доста взиране, може да видим, че това е възможно, като накараме просто число (4
например) само по себе си да е Expression
- например като Constant Int
. Taка Expression
и eval
добиват вида:
data Expr = Constant Int
| Add Expr Expr
eval :: Expr -> Int
eval (Constant x) = x
eval (Add left right) = eval left + eval right
Aналогично може да добавим и другите аритметични операции.
Забележка: Какво става със скобите в изрази и как ги поддържаме?
В момента искаме да изчисляваме данни от тип Еxpression, например Add (Const 3) (Add (Const 5) (Const 2))
. Работа на parser-а е да преобрузава стрингa "3 + (5+2)"
до тази репрезентация.
Нека да добавим и променливи. Отново да започнем с най-простия случай:
x = 2
1 + x
Какво операции относно променливите имаме? Две основни - декларацията (x = 2)
и използването в израз (1 + x)
.
Реално самата декларация не е израз, който връща някакъв резултат като число и моделира изчисление. Да си направим ново ADT
за нея:
data VarDecl = VarDecl String Expr
.
Използването на променлива обаче е израз. Така Expression
става:
data Expr = Constant Int
| Var String
| Arith ArithOp Expr Expr
Резултът от декларацията на променливата е, че вече елементът x
ще асоциираме със стойността 2
. Tрябва някъде да запазим тази инфорамция. Да си дефинираме променлива scope :: [(String, Int)]
, която ще държи тези двойки. Такива променливи обикновено наричаме среда (в която ще се случва изчислението).
Освен това самата ни програма вече не е прост израз, а е последователност от такива декларации и израз най-накрая:
data Program = Program [VarDecl] Expr
Първо да си напишем нова функция, която изпълнява програмата. Освен да оценява израза, ще трябва да обработва дефинициите на променливи и променя средата. След това да променим eval
така че да може да оценява променливи и да се съобразява с текущата среда.
runProgram :: Program -> Int
runProgram (Program decls body) = eval (mkScope [] decls) body
where mkScope sc [] = sc
mkScope sc (VarDecl name expr : xs) = mkScope ((name, eval sc expr):sc) xs
mkScope
е помощната функция, която създава средата.
eval :: Scope -> Expr -> Int
eval scope (Var name) = unbox (lookup name scope)
where unbox (Just val) = val
unbox Nothing = error ("Varible `" ++ name ++ "` not found!")
eval _ (Constant x) = x
....