180 likes | 409 Views
Функциональное программирование. Факультет инноваций и высоких технологий Московский физико-технический институт. Лекция 18. Замыкания, генераторы и отложенные вычисления. λ -исчисление как язык программирования. Синтаксис и семантика. Синтаксис языка был неформально определен раньше
E N D
Функциональное программирование Факультет инноваций и высоких технологий Московский физико-технический институт
Лекция 18 Замыкания, генераторы и отложенные вычисления
λ-исчисление как язык программирования
Синтаксис и семантика • Синтаксис языка был неформально определен раньше • Набор правил, позволяющих транслировать синтаксические улучшения в чистое λ-исчисление • Семантика определяет результат применения программы (функции) ко входным данным • В силу т-мы Черча-Россера, имеет значение порядок редукции • С функциональном языке семантика во многом определяется стратегией редукции (ленивые vs. энергичные вычисления)
Переопределение имени • (λx.(λz.((λx.z)4))(x+1))3→λx.((λz.z)(x+1))3 →(λx.(x+1))3→4 • При переопределении имени новое имя ссылается на новое значение, а все старые ссылки указывают на старые • Переопределение аналогично использованию связанной переменной с тем же именем let x = 3 in let z = x+1 in let x = 4 in z;;
Замыкания • Определяя функцию со свободными переменными, мы также запоминаем состояние этих свободных переменных (контекст) • Пара из функции + контекста называется замыканием (closure, lexical closure) • Используется статическое связывание let x = 4;; let adder y = x+y;; adder 1;; let x = 3;; adder 1;; adder;; val it : (int -> int) = <fun:clo@0>
mutable-переменные • В F# есть механизм, позволяющий осуществлять динамическое связывание(на момент вызова) let mutable x = 4;; let adder y = x+y;; adder 1;; x <- 3;; adder 1;; let x = 4;; let adder y = x+y;; adder 1;; let x = 3;; adder 1;; • В некоторых других языках (ранние версии ЛИСПа) по умолчанию применяется динамическое связывание ввиду его простоты; замыкания создаются в явном виде.
Замыкания как объекты • Замыкание может возвращаться функцией и применяться к аргументу let def_adder n = fun x -> x+n;; let add5 = def_adder 5;; add5 5;; • Замыкание представляет собой результат частичного применения функции (partial evaluation)
Частичное применение • Рассмотрим функцию Аккермана let rec A m n = if m=0 then n+1 else if n=0 then A (m-1) 1 else A (m-1) (A m (n-1));; • Частичное применение: let A3 = A 3;; • Замыкания – это один из способов частичного применения • Возможна оптимизация частичного применения – в этом случае функция заменяется семейством примитивно-рекурсивных функций A0,A1,A2,A3
Специализация • Программа P: I → O • P : Ist → Idyn → O • Специализированная программа Psp = P ist : Idyn → O • Futamura Projection: • Interpreter : Source → Input → Output • Compiled = (Interpreter Source): Input → Output • Суперкомпиляция
Генераторы и объекты • Генератор может быть реализован как closure с mutable (или динамически связываемым) полем • Closure также может применяться для инкапсуляции данных внутри объекта с несколькими функциями доступа Генератор – это функция, которая может производить последовательность значений, возможно бесконечную
Генератор • В F# нельзя напрямую включать mutable-объекты в closure, поэтому приходится использовать immutable запись с mutable-полем type cell = { mutable content : int };; let new_counter n = let x = { content = n } in fun () -> (x.content <- x.content+1; x.content);; let cnt = new_counter 0;; cnt();; 1 cnt();; 2
Ссылочные объекты • Для таких целей F# включает в себя предопределенный тип объектов ref let new_counter n = let x = ref n in fun () -> (x := !x+1; !x);;
Генерализация генератора let new_generator fgen init = let x = ref init in fun () -> (x:=fgen !x; !x);; let fibgen = new_generator (fun (u,v) -> (u+v,u)) (1,1);;
Генераторы и отложенные вычисления • В примере выше мы определили бесконечную последовательность чисел Фибоначчи • С ней можно производить вычисления, записав функциональное выражение в терминах обработки бесконечной последовательности let map f gen = let g = gen in fun () -> f (g());; let rec repeat cond gen = let x = gen() in if cond x then x else repeat cond gen;; let fgen = map (fun (u,v) -> u) fibgen;; repeat (fun x -> x%32) fgen;;
Отложенные вычисления 2 • Мы можем получить по требованию столько членов последовательности, сколько нужно • Программа для решения задачи выглядит (почти) также, как и при использовании списков • Такие вычисления называются также поточными let filter cond gen = let g = gen in fun () -> repeat cond g;; let rec take n gen = if n=0 then [] else gen()::take (n-1) gen;; take 10 (filter (fun x -> x%3=0) fgen);;