1 / 34

Функциональные языки

Функциональные языки. Взгляд изнутри Турдаков Денис ВМиК МГУ / ИСП РАН. План доклада. Краткое введение в Scheme Проблемы и их решения Пример. Краткое введение в Scheme [1]. Код программы на языке Scheme состоит из выражений, которые записываются в префиксной форме

aviva
Download Presentation

Функциональные языки

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Функциональные языки Взгляд изнутри Турдаков Денис ВМиК МГУ / ИСП РАН

  2. План доклада • Краткое введение в Scheme • Проблемы и их решения • Пример

  3. Краткое введение в Scheme [1] Код программы на языке Scheme состоит из выражений, которые записываются в префиксной форме (имя_функции аргументы) Например, математическое выражение 2 + 3 * 4 в Scheme запишется как(+ 2 (* 3 4)) C/C++: ((lower < k) && (k < upper) Scheme: (< lower k upper) C/C++: ((2 * 3) + (5 * 6)) Scheme: (+ (* 2 3) (* 5 6)) C/C++: func(x, y) Scheme: (func x y) C/C++: int sq(int x) {return (x * x)} Scheme: (define (sq x) (* x x))

  4. Краткое введение в Scheme [2] • Функции Scheme – объекты первого класса • (lambda (аргументы) (тело)) • (lambda (x) (* x x)) • (define (sq x) (* x x)) • (define sq (lambda (x) (* x x))) • Пример функции высокого порядка • (map (lambda (x) (* x 2))(list 1 2 3 4 5)) • > результат (2 4 6 8 10)

  5. Краткое введение в Scheme [3] • Определение глобальных переменных: • C/C++: int k 10; • Scheme: (define k 10) • Определение локальных переменных: • C/C++: { • int k 10; • int m 11; • /* некоторый код */ • } • Scheme: (let ((k 10) • (m 11)) • ; некоторый код • )

  6. Краткое введение в Scheme [4] • Условные выражения: • C/C++: if (a > 0) • {/* ветвь 1 */} • else • {/* ветвь 2 */} • Scheme: (if (> a 0) • ( ; ветвь 1) • ( ; ветвь 2) • ) • Массивы • C/C++: int m[] = { 1, 2, 3, 4, 5 }; • m[2] • Scheme: (define m (vector 1 2 3 4 5)) • (vector-ref m 2) ; вернет «3» - элемент массива m с номером 2 (нумерация с нуля)

  7. Основные принципы компиляции программ на языке Scheme Есть в Scheme и нет в С • функции высокого порядка и коррелирующая с ними проблема связывания переменных; • требование эффективно выполнять хвостовые вызовы и связанная с этим проблема роста остаточных вычислений; • возможность работать с остаточными вычислениями как с объектами первого класса, что усложняет предыдущую проблему; • а также, автоматическое управление памятью и сборка мусора.

  8. Связывание переменных • (let ((n 1)) (let ((f (lambda (x) (+ x n))))) • (let ((n 10)) • (f 1))) • Для функции (lambda (x) (+ x n)) • x – связанная переменная • n – свободная переменная • Статическое связывание: => 2 • Динамическое связывание: => 11

  9. Проблема свободных переменных • Проблема: доступ к свободным переменным (lambda (x y) (let ((f (lambda (a b) (+ (* a x) (* b y))))) (- (f 1 2) (f 3 4)))) • Как получить доступ к переменным x и y из тела функции f? • Поместим значения свободных переменных в объект, в котором также будет содержаться код функции. • Поставим задачу реализовать замыкания средствами самого Scheme.

  10. Lambda lifting • Первое решение: добавить значения свободных переменных как параметры (lambda (x y) (let ((f (lambda (x y a b) (+ (* a x) (* b y))))) (- (f x y 1 2) (f x y 3 4)))) • Такое преобразование называется lambda lifting и работает хорошо не всегда (lambda (x y) (let ((f (lambda (a b) (+ (* a x) (* b y))))) f))

  11. Преобразование в замыкания • Второе решение: Построить структуру содержащую свободные переменные и передать ее в функцию как параметр при вызове • Такой объект называетсязамыкание (closure). (lambda (x y) (let ((f (vector (lambda (self a b) (+ (* a (vector-ref self 1)) (* b (vector-ref self 2)))) x y))) (- ((vector-ref f 0) f 1 2) ((vector-ref f 0) f 3 4))))

  12. Правила преобразования • (lambda (P1 … Pn) E)= • (vector (lambda (self P1 … Pn) E) v…) • где v… список свободных переменных функции (lambda (P1 … Pn) E) • v = (vector-ref self i) • где vсвободная переменная, аi– позиция v в списке свободных переменных незамкнутого lambda-выражения • (f E1... En) = ((vector-reff 0) f E1 … En )

  13. Рост остаточных вычислений (define fact-iter (lambda (n) (fact-iter-acc n 1))) (define fact-iter-acc (lambda n a) (if (zero? n) a (fact-iter-acc (- n 1) (* n a))))) (fact-iter 4) =(fact-iter-acc 4 1) =(fact-iter-acc 3 4) =(fact-iter-acc 2 12) =(fact-iter-acc 1 24) =(fact-iter-acc 0 24) =24 (define fact (lambda (n) (if (zero? n) 1 ( * n (fact (- n 1)))))) (fact 4) =(* 4 (fact 3)) =(* 4 (* 3 (fact 2))) =(* 4 (* 3 (* 2 (fact 1)))) =(* 4 (* 3 (* 2 (* 1 (fact 0))))) =(* 4 (* 3 (* 2 (* 1 1)))) =(* 4 (* 3 (* 2 1))) =(* 4 (* 3 2)) =(* 4 6) =24

  14. Рост стека тв3 Первый случай 2 тв2 тв2 3 3 тв1 тв1 тв1 … 4 4 4 Второй случай 1 4 12 24 24 4 4 3 2 1 0

  15. Критерии роста • Остаточные вычисления не растут тогда и только тогда, когда все вызовы хвостовые • Строгое определение хвостовых вызовов задается синтаксически

  16. Хвостовые вызовы • Последнее выражение в теле lambda-выражения – хвостовое <tail expression> • (lambda <formals> <definition>* <expression>* <tail expression>) • Если одно из следующих выражений хвостовое, то подвыражение, показанное, как <tail expression>, тоже хвостовое • (if <expression> <tail expression> <tail expression>) • (if <expression> <tail expression>) • (cond <cond clause>+) • (cond <cond clause>* (else <tail sequence>)) • (case <expression> <case clause>+) • (case <expression> <case clause>* (else <tail sequence>)) • (and <expression>* <tail expression>) • (or <expression>* <tail expression>) • (let (<binding spec>*) <tail body>) • (let <variable> (<binding spec>*) <tail body>) • (let* (<binding spec>*) <tail body>) (letrec (<binding spec>*) <tail body>) (let-syntax (<syntax spec>*) <tail body>) (letrec-syntax (<syntax spec>*) <tail body>) (begin <tail sequence>) (do (<iteration spec>*) (<test> <tail sequence>) <expression>*) где <cond clause> ---> (<test> <tail sequence>) <case clause> ---> ((<datum>*) <tail sequence>) <tail body> ---> <definition>* <tail sequence> <tail sequence> ---> <expression>* <tail expression>

  17. Остаточные вычисления • Остаточные вычисления (Continuations) – что-то, что ждет значение. Например, следующая программа: (sqrt (+ (read) 1)) • будет ждать, пока пользователь не введет число. • Остаточные вычисления для функции foo в выражении (* (foo 1) (+ 5 1)) – (lambda (x) (* x (+ 5 1))) • Остаточные вычисления – динамические объекты. На каждом шаге программы существуют текущие (current) остаточные вычисления • Остаточные вычисления в Scheme являются объектами первого класса

  18. Остаточные вычисления,как объекты первого класса • (call/cc (lambda (cont) <body>)) – превращает остаточные вычисленияв функцию c одним параметром (sqrt (+ (call/cc (lambda (cont) (* 2 (cont 8)))) 1)) (sqrt (+ 8 1)) => (define saved-cont #f ) (display (call/cc (lambda (cont) (set! saved-cont cont)))) (saved-cont 8) (saved-cont 16)

  19. Пример 1 (define saved-cont #f ) (display (* 2 (call/cc (lambda (cont) (set! saved-cont cont))) )) (saved-cont 8); => 16 (saved-cont 16); => 32

  20. Пример 2 Обработка исключений: (define (map-/ lst) (call/cc (lambda (return) (map (lambda (x) (if (= x 0) (return #f) (/ 1 x))) lst)))) (map-/ '(1 2 3)) => (1 1/2 1/3) (map-/ '(1 0 3)) => #f

  21. CPS-преобразование (1) • Итог: мы показали, что • 1) Рост остаточных вычислений приводит к неэффективному использованию памяти • 2) Наличие call/cc приводит к тому, что остаточные вычисления • Могут вызываться более одного раза.Пример X2 = Y2 + Z2 • Существовать неограниченное время • Становится очевидной необходимость преобразования программы к виду, где все вызовы являются хвостовыми • Самое удачное решение – выделение остаточных вычислений в отдельные функции • Пример: • (let ((square (lambda (x) (* x x)))) • (display (+ (square 10) 1))) • Остаточные вычисления для(square 10)будут ждать значение, прибавит к нему 1 и выведет результат

  22. CPS-преобразование (2) • Остаточные вычисленияпредставляются функцией • (lambda (r) (display (+ r 1))) • Эти остаточные вычислениянеобходимо передать square, и функция сможет переслать результат • Итак, мы должны добавить continuation-параметр всем lambda-выражениям, изменить вызовы функций для передачи continuation и использовать continuation каждый раз, когда должны вернуть результат. • (CPS = Continuation-Passing Style) • (let ((square (lambda (k x) (k (* x x))))) • (square (lambda (r) (display (+ r 1))) • 10))

  23. СущностьCPS-преобразования Пример: (let ((mult (lambda (a b) (* a b)))) (let ((square (lambda (x) (mult x x)))) (display (+ (square 10) 1)))) преобразовывается в (let ((mult (lambda (k a b) (k (* a b))))) (let ((square (lambda (k x) (mult k x x)) (square (lambda (r) (display (+ r 1))) 10))) вызов mult в square – хвостовой, поэтому mult имеет такие же остаточные вычисления, как и square, а для нехвостовых вызовов через continuation-параметр передаются все остаточные вычисления

  24. РезультатCPS-преобразования • Когда CPS-преобразование будет окончено все вызовы функций приобретут хвостовую форму • Вызовы функций могут быть легко представлены в виде переходов (jumps) с текущим набором параметров, то есть не надо хранить в стеке (при стековой модели вычислений) точку возврата и предыдущий контекст

  25. Правила CPS-преобразования E С Обозначение: - CPS-преобразование выражения E, где С – continuation E Е – исходное выражение (может содержать не хвостовые вызовы). С – выражение в CPS-форме (содержит только хвостовые вызовы) С – либо переменная либо lambda-выражение program = program (lambda (r) (halt r)) Первое правило: Означает что самый первый continuation программы получает r, результат программы, и вызывает операцию (halt r) завершающую вычисления.

  26. Правила CPS-преобразования • v = (С v) • C • (set! v E1) = E1 • C (lambda (r) • (C (set! v r))) • (if E1 E2 E3) = E1 • C (lambda (r) (if r E2 E3 )) • C C • (begin E1 E2) = E1 • C (lambda (r) E2 ) • C • (lambda (P1 … Pn) E0) = • C (C (lambda (k P1 … Pn) E0 ) • k

  27. Правила CPS-преобразования • (+ E1 E2) = E1 • C (lambda (r1) E2 ) • (lambda (r2) (C (+ r1 r2))) • (E0) = E0 • C (lambda (r) (r C)) • (E0 E1) = E0 • C (lambda (r0) E1 ) • (lambda (r1) (r0 C r1)) • (E0 E1 E2) = E0 • C (lambda (r0) E1 ) • (lambda (r1) E2 ) • и т. д. (lambda (r2) (r0 C r1 r2))

  28. Правила CPS-преобразования • ((lambda () E0)) = E0 • C C • ((lambda (P1) E0) E1) = E1 • C (lambda (P1) E0) • C • ((lambda (P1 P2) E0) E1 E2) = • C • E1 • (lambda (P1) E2 ) • (lambda (P2) E0 ) • C • и т. д.

  29. сall/cc и CPS-преобразование • (sqrt (+ (call/cc • (lambda (cont (* 2 (cont 8)))) 1)) • Преобразование call/cc: • (define (cps-call/cc k consumer) • (let ((reified-current-continuation • (lambda (k1 v) (k v)))) • (consumer k reified-current-continuation)) • Это определение добавляется в текст программы если используется call/cc • (call/cc-cps (lambda (r0) • (lambda (r1) (sqrt r1)) (+ r0 1)) • (lambda (C cont) (C (* 2 • (cont (lambda (x) x) 8)))))

  30. Трансляция в С • closure-passing style • GambitScheme • GVM • Регистры: В R0 – метка, В R1..RN – параметры • Глобальные переменные и метки заносятся в таблицы с помощью специальных команд • В начале каждой функции ставится глобальная метка, с помощью которой можно вызвать функцию. • После завершения работы операции, результат возвращается в регистре R1.

  31. Пример [1] • Исходный текст: • (define square (lambda (x) (* x x)) • (+ (square 5) 1) • После CPS-преобразования: • (define square (lambda (r1 x) (r1 (* x x)))) • (square (lambda (r3) • (let ((r2 (+ r3 1))) (halt r2))) 5) • После преобразования замыканий • (define square (vector (lambda (self1 r1 x) • ((vector-ref r1 0) r1 (* x x))))) • ((vector-ref square 0) square • (vector (lambda (self2 r3) (let ((r2 (+ r3 1))) (halt r2)))) 5)

  32. Пример [2] #include “gambit.h” – содержит описание всех используемых макросов … - некоторые определения, не существенные для данной статьи Далее идут команды виртуальной машины, которые являются макросами языка C. Их расшифровка представляет собой чисто техническую задачу, и любознательный читатель может посмотреть исходный код Gambit Scheme. BEGIN_P_COD – начало программы DEF_GLBL (L_MAIN) – метка на начало программы SET_STK (1, R0) – поместить в вершину стека регистр R0 SET_R1 (FIX (5L)) – положить в регистр R1 значение 5 SET_R0 (LBL (1)) – после выполнения операции совершим переход на метку 1 OP_JMP (PARAMS (2), G_SQUARE) – выполнить двухместную операцию G_SQUARE и перейти на метку, содержащуюся в R0 DEF_SLBL (1, _MAIN_1) – локальная метка в теле программы с номером 1 SET_R2 (FIX (1L)) – положить в регистр R2 значение 1 SET_R0 (STK (-1)) – взять из стека метку конца программы и положить ее в R0 OP_JMP (PARAMS (2), _plus) – сложить значения в регистрах R1 и R2, результат поместить в регистр R1 и прейти по метке в регистре R0 (конец программы) END_P_COD – конец программы BEGIN_P_COD – начало функции SQUARE DEF_GLBL (L_SQUARE) – метка на начало функции SQUARE SET_R2 (R1) – поместить в регистр R2 такое же значение как и в R1 OP_JMP (PARAMS (2), mul) – перемножить регистры R1 и R2, результат положить в регистр R1 и перейти по метке в регистре R0 (на метку _MAIN_1) END_P_COD – конецфункции SQUARE

  33. Литература • “Essential Of Programming Languages”, second edition, Daniel P. Friedman, Mitchell Wand, Christopher T. Haynes, The MIT Press, Cambridge, Massachusetts, London, Engalnd, 2001. • “Continuations”, Shriram Krishnamurthi, 2001-10-12 • “CONS Should not CONS its Arguments, or, a Lazy Alloc is a Smart Alloc”, Henry G. Baker, ACM Sigplan Notices 27, 3 (Mar 1992), 24-34 • “The 90 minute Scheme to C compiler”, Mark Feeley, Universite de Montreal • “Rabbit: A compiler for Scheme”, Guy Lewis Steele, MIT Artificial Intelligence Laboratory • “A Runtime System”, Andrew W. Appel, Princeton University, CS-TR-220-89, May 1989 • “On the Overhead of CPS”, Oliver Danvy, Belmina Dzafic, Frank Pfenning, 1996-11-18 • “Implementation Strategies for Continuations”, Willam D. Ctinger, Erie M. Ost, 1988 ACM 0-09791-273-X/88/0007/0124 • “Orbit: An Optimizing Compiler for Scheme”, David Kranz, Richard Kelsey, Jonathan Rees, Paul Hudak, ACM Sigplan, Best of PLDI 1979-1999, 175-191 • “CPS Recursive Ascent Parsing”, Arthur Nunes-Harwitt • “Shift to control”, Chung-chieh Shan, Harvard University • “Continuation-Passing, Closure-Passing Style”, Andrew W. Appel, Trevor Jimt, CS-TR-183-88, 1988 • Three Steps for the CPS Transformation (detail abstract)”, Oliver Danvy, Department of Computing and Information Science, Kansas State University, 1991-12

  34. Спасибо • e-mail: turdakov@gmail.com • Презентация доступна на сайте http://modis.ispras.ru/turdakov/fl.ppt

More Related