160 likes | 322 Vues
5.3. Eval / Apply- интерпретатор. Интерпретация, основанная на контексте. Контекст – иерархический ассоциативный список связей имен переменных со значениями. type Context = [(String, Expr)]. assoc :: String -> Context -> Expr.
E N D
5.3. Eval / Apply-интерпретатор Интерпретация, основанная на контексте. Контекст – иерархический ассоциативный список связей имен переменных со значениями. type Context = [(String, Expr)] assoc :: String -> Context -> Expr assoc x ((y,e):ctx) | x == y = e | otherwise = assoc x ctx -- вычисление значения выражения в контексте (приведение к СЗНФ): eval :: Context -> Expr -> Expr -- вычисление результата применения функции к аргументу: apply :: Expr -> Expr -> Expr -- интерпретация: interpreter :: Expr -> Expr interpreter = eval [] Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
5.3. Eval / Apply-интерпретатор (продолжение) data Expr = Integral Integer | Logical Bool | Function String -- константы | Variable String -- переменная | Lambda String Expr -- лямбда-выражение | Apply Expr Expr -- применение функции | Let String Expr Expr | Letrec [(String, Expr)] Expr -- блоки | Closure String Expr Context -- замыкание | Oper Int String [Expr] -- сечение eval _ e@(Integral _) = e eval _ e@(Logical _) = e eval _ (Function f) = Oper (arity f) f [] eval ctx (Lambda x e) = Closure x e ctx eval _ e@(Closure _ _ _) = e eval _ e@(Oper _ _ _) = e eval ctx (Variable x) = assoc x ctx eval ctx (Apply f a) = apply (eval ctx f) (eval ctx a) apply (Closure x body ctx) arg = eval nc body where nc = (x, arg) : ctx apply (Oper n f la) a | n == 1 = intrinsic f newListArgs | otherwise = Oper (n-1) f newListArgs where newListArgs = la ++ [a] Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
5.3. Eval / Apply-интерпретатор (продолжение) eval ctx (Let x arg body) = eval newCtx body where newCtx = ((x, (eval ctx arg)):ctx) let x=arg in body ~ (λx.body) arg (Let x arg body) ~ (Apply (Lambda x body) arg) eval ctx (Let x arg body) = apply (eval ctx (Lambda x body)) (eval ctx arg) = apply (Closure x body ctx) (eval ctx arg) eval ctx (Letrec args body) = eval newCtx body where newCtx = (map (\(x,arg) -> (x, eval newCtx arg)) args) ++ ctx Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Энергичный vs. ленивый интерпретатор Механизм интерпретации определяет реализованную схему! eval ctx (Apply f a) = apply (eval ctx f) (eval ctx a) Вычисление аргумента происходит до вызова applyпри энергичной реализации, но задерживается до первого обращения к нему при ленивой реализации инструментального языка. Если инструментальный язык энергичный, то дополнительные проблемы – это: • реализация стандартной функции IF; • реализация рекурсивного блока. data Expr = ... | If Expr Expr Expr -- условное выражение eval ctx (If p t e) = if (eval ctx p) == (Logical True) then eval ctx t else eval ctx e eval ctx (If p t e) = eval ctx (if eval ctx p then t else e) «Зацикленный» контекст, использующийся для реализации рекурсивного блока,вообще не реализуем в чисто энергичном функциональном языке! В языке Haskellэнергичный интерпретатор можно реализовать с помощью «строгих» применений: eval ctx (Apply f a) = (apply $! (eval ctx f)) $! (eval ctx a) Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Реализация встроенных функций apply (Oper nArgs f argsList) arg | nArgs == 1 = intrinsic f newList| otherwise = Oper (nArgs-1) f newListwhere newListArgs = argsList ++ [arg] intrinsic "+" [Integral(a), Integral(b)] = Integral (a+b) intrinsic "-" [Integral(a), Integral(b)] = Integral (a-b) intrinsic "*" [Integral(a), Integral(b)] = Integral (a*b) intrinsic "/" [Integral(a), Integral(b)] = Integral (a `div` b) intrinsic "EQ0" [Integral(a)] = Logical (a==0) intrinsic "SUCC" [Integral(a)] = Integral (a+1) intrinsic "PRED" [Integral(a)] = Integral (a-1) eval _ (Function f) = Oper (arity f) f [] arity "+" = 2 arity "-" = 2 arity "*" = 2 arity "/" = 2 arity "EQ0" = 1 arity "SUCC" = 1 arity "PRED" = 1 Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Пример интерпретации простой программы На языке Haskell: sqr :: Integer -> Integersqr x = x*xinterpret: sqr 3 В расширенном лямбда-исчислении: let sqr = λx.* x x in (sqr 3) Представление в виде выражения типа Exprв языке Haskell: prog :: Exprprog = (Let "sqr" (Lambda "x" (Apply (Apply (Function "*“) (Variable "x")) (Variable "x"))) (Apply (Variable "sqr") (Integral 3))) Что получится в результате вызова interpreter prog ? Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Пример интерпретации простой программы prog = (Let "sqr" (Lambda "x" (Apply (Apply (Function "*") (Variable "x")) (Variable "x"))) (Apply (Variable "sqr") (Integral 3))) interpreter prog eval [] prog eval [] (Let "sqr" (Lambda ...) (Apply ...)) apply (Closure "sqr" (Apply ...) []) (eval [] (Lambda "x" ...)) apply (Closure "sqr" (Apply ...) []) (Closure "x" (Apply ...) []) eval [("sqr",(Closure "x" (Apply ...) []))] (Apply (Variable "sqr") (Integral 3)) apply (eval [("sqr",(Closure "x" (Apply ...) []))] (Variable "sqr") (eval [("sqr",(Closure "x" (Apply ...) []))] (Integral 3)) apply (Closure "x" (Apply ...) []) (Integral 3)) eval [("x",(Integral 3)] (Apply (Apply (Function "*") (Variable "x")) (Variable "x")) apply (eval [("x",(Integral 3)] (Apply (Function "*") (Variable "x"))) (eval [("x",(Integral 3)] (Variable "x")) Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Пример интерпретации простой программы (продолжение) apply (eval [("x",(Integral 3)] (Apply (Function "*") (Variable "x"))) (eval [("x",(Integral 3)] (Variable "x")) apply (apply (eval [("x",(Integral 3)] (Function "*")) (eval [("x",(Integral 3)] (Variable "x"))) (Integral 3) apply (apply (Oper (arity "*") "*" []) (Integral 3)) (Integral 3) apply (apply (Oper 2 "*" []) (Integral 3)) (Integral 3) apply (Oper 1 "*" [(Integral 3)]) (Integral 3) intrinsic "*" [(Integral 3),(Integral 3)] (Integral 9) Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Глава 5. Системы исполнения функциональных программ 5.3. SECD-машина. Попробуем транслировать конструкции лямбда-исчисления в еще более простой язык,интерпретатор которого допускает простое и однозначное толкование. S – Stack – содержит промежуточные результаты вычислений в СЗНФ E – Environment – содержит контекст вычислений C – Control – содержит последовательность команд D – Dump – содержит состояния машины type Stack = [WHNF] type Environment = [(String, WHNF)] type Control = [Command] type Dump = [(Stack, Environment, Control)] data Command = Integral Integer | Boolean Bool | Function String | Variable String | Lambda String Command | Apply Command Command | If Command Command Command | Let String Command Command | Letrec [(String, Command)] Command data WHNF = C_Int Integer | C_Bool Bool | Closure String Environment Command | Oper String Int [WHNF] data SECD = (Stack, Environment, Control, Dump) Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Работа SECD-машины. Переход из состояния в состояние: (s, e, c, d) (s', e', c', d') Функциональное выражение для процесса переходов: evaluate :: SECD -> SECD Уравнения функции evaluateбудут иметь следующий вид: evaluate (s, e, c, d) = evaluate (s', e', c', d') или, в случае, когда состояние (s, e, c, d) заключительное: evaluate (s, e, c, d) = (s, e, c, d) Интерпретатор создает SECD-машину в начальном состоянии, запускает ее, вызывая функцию evaluate, и извлекает результат вычислений из SECD-машины в конечном состоянии: interpret :: Command -> WHNF interpret com= res where (res:_, _, _, _) = evaluate ([], [], [com], []) то есть исходная программа помещается в регистр управления, а результат извлекается с вершины стека выражений. Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Уравнения функции evaluate, описывающей работу SECD-машины. Команды, представляющие выражения, уже находящиеся в СЗНФ, просто перекладываютэти выражения на вершину стека вычислений, изменяя их представление. evaluate (s, e, (Integral n):c, d) = evaluate ((C_Int n):s, e, c, d) evaluate (s, e, (Boolean b):c, d) = evaluate ((C_Bool b):s, e, c, d) evaluate (s, e, (Lambda x body):c, d) = evaluate ((Closure x body e):s, e, c, d) evaluate (s, e, (Function f):c, d) = evaluate ((Oper (arity f) f []):s, e, c, d) evaluate (s, e, (Variable x):c, d) = evaluate ((assoc x e):s, e, c, d) Исполнение команды условного вычисления If: evaluate (s, e, (If cond the els):c, d) = evaluate (s, e, cond:(Select the els):c, d) evaluate ((C_Bool True):s, e, (Select the els):c, d) = evaluate (s, e, the:c, d) evaluate ((C_Bool False):s, e, (Select the els):c, d) = evaluate (s, e, els:c, d) Здесь Select – это новая специальная команда условного перехода: data Command = ... | Select Command Command Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Уравнения функции evaluateдля более сложных команд. Применение функции: evaluate (s, e, (Apply f a):c, d) = evaluate (s, e, a:f:App:c, d) Здесь App – это новая специальная команда применения функции: data Command = ... | App Исполнение команды Appдля случая примитивной функции: evaluate ((Oper n f args):arg:s, e, App:c, d) | n == 1 = evaluate ((intrinsic f newArgs):s, e, c, d) | otherwise = evaluate ((Oper (n-1) f newArgs):s, e, c, d) where newArgs = args ++ [arg] Исполнение команды Appдля замыкания(вход в функцию и выход из нее): evaluate ((Closure x body ctx):a:s, e, App:c, d) = evaluate ([], (x, a):ctx, [body], (s, e, c):d) evaluate (x:_, _, [], (s, e, c):d) = evaluate (x:s, e, c, d) Аналогично для простого (нерекурсивного) блока: data Command = ... | LApp String Command evaluate (s, e, (Let x exp body):c, d) = evaluate (s, e, exp:(LApp x body):c, d) evaluate (arg:s, e, (LApp x body):c, d) = evaluate ([], (x,arg):e, [body], (s, e, c):d) Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Интерпретация простого и рекурсивного блоков. Рассмотрим последовательность состояний при исполнении простого блока: STK, ENV, (Let x exp body):COM, DUMP STK, ENV, exp:(LApp x body):COM, DUMP exp':STK, ENV, (LApp x body):COM, DUMP [], (x,exp'):ENV, [body], (STK, ENV, COM):DUMP [body'], (x,exp'):ENV, [], (STK, ENV, COM):DUMP body':STK, ENV, COM, DUMP Последовательность состояний при исполнении рекурсивного блока должна отличатьсятем, что вычисление связываемых значений должно проводиться в уже пополненном контексте: STK, ENV, (Letrec [(x1,e1),(x2,e2),...(xn,en)] body):COM, DUMP STK, (x1,?):(x2,?):...(xn,?):ENV, en:...e2:e1:(LRApp body):COM, DUMP e1':e2':...en':STK, (x1,?):(x2,?):...(xn,?):ENV, (LRApp body):COM, DUMP [], (x1,e1'):(x2,e2'):...(xn,en'):ENV, [body], (STK,ENV,COM):DUMP [body'], (x1,e1'):(x2,e2'):...(xn,en'):ENV, [], (STK,ENV,COM):DUMP body':STK, ENV, COM, DUMP Здесь самый тонкий момент – это замена значений в уже сформированном контексте. evaluate (s, e, (Letrec pairs body):c, d) = evaluate (s, newPairs ++ e, (reverse exprs) ++ ((LRApp n body):c), d) where (newPairs, n) = (map (\n -> (n, ?)) names, length names) (names, exprs) = unzip pairs evaluate (s, e, (LRApp n body):c, d) = evaluate ([], replaceValues n s e, [body], (drop n s, drop n e, c):d) Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Реализация псевдо-функции replaceValues. e1':e2':...en':STK, (x1, ):(x2, ):...(xn, ):ENV, (LRApp body):COM, DUMP e1' ? e2' ? en' ? Если в выражениях e1', e2', en'имеются копии контекста, то замена значений с помощью псевдо-функции replaceValuesповлияет сразу на все копии. Реализованная SECD-машина – энергичная, однако, и здесь ленивый инструментальный язык реализации привносит «ленивость» в процесс интерпретации. Например, помещаемые в стекрезультаты вычислений функции intrinsicна самом деле будут получены, только если ониреально потребуются для представления результата. Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Ленивая версия SECD-машины. Введем понятие задержанного результата для того, чтобы задержать вычисление значения выражения до момента первого обращения к нему. data WHNF = C_Int Integer | C_Bool Bool | Closure String Environment Command | Oper String Int [WHNF]| Delay Environment Command Применение функциидля случая ленивых вычислений: evaluate (s, e, (Apply f a):c, d) = evaluate ((Delay e a):s, e, f:App:c, d) Вычисление задержанного результата будет происходить при выполнении встроенных операций,таких как арифметические операторы или индексация кортежа. evaluate (func@(Oper n f args):(Delay env com):s, e, App:c, d) = evaluate ([], env, [com], (func:s, e, c):d) При возврате вычисленное значение выражения помещается на место аргумента функции,и применение функции повторяется уже к вычисленному значению: evaluate ([res], _, [], (func:s, e, c):d) = evaluate (func:res:s, e, App:c, d) Для того, чтобы избежать повторного вычисления одних и тех же значений, задержки помещаютсяв отдельную область памяти, а в стек вместо нее помещается указатель. Когда значение будетвычислено, результат должен быть помещен на место задержки с помощью присваивания. Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.
Реализация рекурсивного блока в ленивой версии SECD-машины. evaluate (s, e, (Letrec pairs body):c, d) = evaluate ([], newEnv, [body], (s, e, c):d) where newEnv = (map (\(n, v) -> (n, (Delay newEnv v)) pairs) ++ e Здесь в новом контексте newEnvпоявятся задержки, содержащие ссылку на тот же самый контекст. Реализация простого блока Letостается без изменений. Выводы: • SECD-машина удобна для описания последовательности команд для вычислений. • Энергичная SECD-машина хорошо реализуется в энергичном языке, однако реализация рекурсивного блока требует исполнения присваиваний; для ленивого инструментального языка требуется моделирование энергичных вычислений. • Реализация ленивой SECD-машины требует реализации механизма задержанных вычислений с присваиванием. Кубенский А.А. Функциональное программирование. Глава 5. Системы исполнения функциональных программ.