190 likes | 399 Views
Курс лекций для студентов Computer Science центра. Функциональное программирование. Действие. Вход. Выход. Выбор. «Черный ящик». Если «да». Если «нет». Функция. Алгоритм. Глава 1. Элементы функционального программирования. 1.1. Введение в функциональное программирование.
E N D
Курс лекций для студентов Computer Science центра Функциональное программирование
Действие Вход Выход Выбор «Черный ящик» Если «да» Если «нет» Функция Алгоритм Глава 1. Элементы функционального программирования 1.1. Введение в функциональное программирование • Архитектура фон Неймана диктует стиль программирования? • Средства программирования: • Арифметические и логические операции; • Присваивания; • Последовательное исполнение шагов алгоритма; • Управление (управляющие конструкции); • Процедуры и функции; • Модули, исключительные ситуации, структуры данных,... • Программа: описание процесса (алгоритма) или «черный ящик»? Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Задача о вычислении значений квадратных корней уравнения (процедурный стиль программирования) /** Процедура вычисляет вещественные или комплексные корни квадратного трехчлена, * в предположении, что первый коэффициент (a) отличен от нуля. * Аргументы: a, b, c – коэффициенты квадратного трехчлена; * Результаты: complexFlag – признак комплексных корней; * r1, r2 – вещественные корни, если complexFlag = False и *вещественная и мнимая части двух корней, если complexFlag = True */ class Result { booleancomplexFlag; double r1; double r2; } static double discriminant (double a, double b, double c) { return b * b – 4 * a * c; } static Result squareRoots (double a, double b, double c) { Result result = new Result(); doublediscr = discriminant (a, b, c); // Вычисляем дискриминант result.complexFlag = discr < 0; // Определяем, вещественные или мнимые корни if (result.complexFlag) { result.r1 = (-b) / (2*a); // Вещественная часть корней result.r2 = Math.sqrt(-discr) / (2*a); // Мнимая часть корней } else { result.r1 = (-b + Math.sqrt(discr)) / (2*a); // Первый вещественный корень result.r2 = (-b – Math.sqrt(discr)) / (2*a); // Второй вещественный корень }return result; } Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Та же программа, написанная в функциональном стиле программирования (на псевдоязыке с Java-подобным синтаксисом) /** Функция вычисляет вещественные или комплексные корни квадратного трехчлена, * в предположении, что первый коэффициент (a) отличен от нуля. * Аргументы: a, b, c – коэффициенты квадратного трехчлена; * Результаты: признак комплексных корней; * вещественные корни, если они вещественные, и * вещественная и мнимая части двух корней, если мнимые */ double discriminant (double a, double b, double c) { return b * b – 4 * a * c; } (boolean, double, double)squareRoots (double a, double b, double c) { final double discr = discriminant(a, b, c); // Значение дискриминанта final double complexFlag = discr < 0; // Определяем, вещественные или мнимые корни (complexFlag, (complexFlag ?((-b) / (2*a), sqrt(-discr) / (2*a)) : ((-b + sqrt(discr)) / (2*a), (-b – sqrt(discr)) / (2*a)) ); } Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Особенности этой программы Составные значения легко описываются... Вместо переменных и присваиваний используются константы Константы получают динамически вычисляемые значения ...и создаются double discriminant (double a, double b, double c) { return b * b – 4 * a * c; } (boolean, double, double)squareRoots (double a, double b, double c) { final double discr = discriminant(a, b, c); // Значение дискриминанта final double complexFlag = discr < 0; // Определяем, вещественные или мнимые корни (complexFlag, (complexFlag ?((-b) / (2*a), sqrt(-discr) / (2*a)) : ((-b + sqrt(discr)) / (2*a), (-b – sqrt(discr)) / (2*a)) ); } Широко используются условные выражения Результаты не зависят от порядка вычислений (возможны параллельные вычисления) Тело функции представляет собой суперпозицию применений функций для описания функциональной зависимости результата от входных данных Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Функциональное представление множеств boolean intSet (int); // описание функционального типа данных boolean emptySet (int n) { returnfalse; } // пустое множество boolean oddSet (int n) { return n % 2 == 1; } // множество нечетных чисел Несколько полезных операций над множествами intSet addElement (intSet s; int newElem) { boolean newSet (int n) { return s(n) || (n == newElem); } return newSet; } intSet buildInterval (int min, int max) { boolean newSet (int n) { return (n >= min) && (n <= max); } return newSet; } intSet difference (intSet s1, intSet s2) { boolean newSet (int n) { return s1(n) &&! s2(n); } return newSet; } Будут ли работать эти операции? Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
addElement boolean emptySet (int n) { returnfalse; } s = emptySet newElem = 3 boolean oddSet (int n) { return n % 2 == 1; } newSet newSet intSet addElement (intSet s, int newElem) { boolean newSet (int n) { return s(n) || (n == newElem); } return newSet; } difference s1 = oddSet s2 = addElement.newSet intSet difference (intSet s1, intSet s2) { boolean newSet (int n) { return s1(n) && ! s2(n); } return newSet; } Попробуем вычислить выражение difference (oddSet, addElement (emptySet, 3)) (7) { Принадлежит ли 7 множеству [odds] \ [3] } Стек вычислений Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Функциональное представление множествв языке Java В языке Javaвсе же можно построить правильную программу для работы с функциональным представлением множеств: abstractclass IntSet { abstractboolean has(int elem); publicstatic IntSet addElement(final IntSet s, finalint n) { returnnew IntSet() { publicboolean has(int elem) { return elem == n || s.has(elem); } }; } publicstatic IntSet buildInterval(finalint n, finalint m) { returnnew IntSet() { publicboolean has(int elem) { return elem >= n && elem <= m; } }; } publicstatic IntSet difference(final IntSet s1, final IntSet s2) { returnnew IntSet() { publicboolean has(int elem) { return s1.has(elem) && ! s2.has(elem); } }; } publicstaticvoid main(String[] args) { IntSet emptySet = new IntSet() { publicboolean has(int e) { returnfalse; }}; IntSet oddSet = new IntSet() { publicboolean has(int e) { return e % 2 == 1; }}; System.out.println(difference(oddSet, addElement(emptySet, 3)).has(7)); } } Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Подведение итогов • Императивные языки служат для описания процессов; функциональные – для описания функций, вычисляющих результат по исходным данным. • На традиционных языках можно писать в функциональном стиле, однако средств работы с функциями в традиционных языках недостаточноили они неудобны. • Традиционные способы реализации языков программирования плохо подходят для программ, написанных в функциональном стиле. • Традиционные языки не могут обеспечить удобных средств для распараллеливания вычислений: последовательное выполнение команд – узкое место традиционной архитектуры компьютеров («фон-Неймановское горлышко»). • Для функционального программирования требуются специализированные языки Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Литература Основная • А. Филд, П. Харрисон. Функциональное программирование. М.: Мир, 1993. • П. Хендерсон. Функциональное программирование. Применение и реализация.М.: Мир, 1983. • А.А.Кубенский. Функциональное программирование. ИТМО, 2010. Дополнительная • Пол Хьюдак, Джон Петерсон, Джозеф Фасел. Мягкое введение в Haskell.RSDN Magazin, 2006 № 4 и 2007 № 1(http://rsdn.ru/article/haskell/haskell_part1.xml, http://rsdn.ru/article/haskell/haskell_part2.xml) • Р. В. Душкин Функциональное программирование на языке Haskell (+ CD-ROM)Издательство: ДМК Пресс, 2007 г., Мягкая обложка, 608 стр. • Н.А.Роганова. Функциональное программирование. Учебник для вузов, 2002. • Graham Hutton. Programming in Haskell. 2007 г., Мягкая обложка, 184 стр. • Bryan O'Sullivan, John Goerzen, Donald Stewart. Real World Haskell: Code You Can Believe In. Издательство: O'Reilly Media, 2008 г., Мягкая обложка, 710 стр. Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
1.2. Введение в язык Haskell История языка Haskell • начало 1930-х: Church, формализация функций в λ-исчислении • 1960: John McCarthy, LISP – первый функциональный язык программирования • 1978: John Backus, FP – система комбинаторного программирования • конец 1970-х: Edinburgh univ., ML – meta-language • 1985-1986: David Turner, Miranda – функциональный язык с «ленивыми» вычислениями • 1990: Ericsson, Erlang – «коммерческий» функциональный язык • 1988: Paul Hudak, Haskell – первая версия языка Haskell • 1999: Haskell group, Haskell’98 – «стандартная» версия языка Haskell http://haskell.org/onlinereport/index.html - пересмотренное сообщение о языке Haskell’98 http://haskell.org/tutorial- «Нежное» введение в Haskell Haskell – чисто функциональный язык программирования, названный в честь Хаскелла Карри (Haskell B. Curry – 1900-1982), известного, главным образом, благодаря работам в области математической логики и комбинаторной логики в конце 1950-х – начале 1960-х годов Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Типы данных и базовые конструкции языка Haskell Элементарные типы данных • Integer, Int – целые значения (25, -17, 111222333444555666777888) • Float, Double – вещественные значения (3.14, -2.718281828459045) • Char – символьные значения ('A', '*', '3') • Bool – логические значения (True, False) Идентификаторы: fact, fAcToRiAl, fact_1, fact'' Знаки операций: +, -, *, <, == Идентификаторы применяются для обозначения констант – значений разных типов (простых, составных, функций)и типов. Любому идентификатору можно сопоставить тип и значение: school :: Integerschool = 239 piHalf :: DoublepiHalf = 3.1415926536 / 2 Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Кортежи и функции Значения могут объединяться в более сложные с помощью кортежирования. Например: pair :: (Double, Double)pair = (2.7, 3.14) attributes :: (Char, (Int, Int, Int), Bool)attributes = ('M', (17, 4, 1955), True) Тип функции определяется типами аргументов и результата, например: sin :: Double -> Double -- аргумент и результат типа DoubleplusInt :: Int -> Int -> Int -- два аргумента типа Int, результат IntdivMod :: (Int, Int) -> (Int, Int) -- аргумент и результат - кортежи Выражения составляются из констант применением операций и функций, например: result = sin (3.1416 / 4) - 2.5c10 = 3 + plusInt 3 4pair = divMod (1458, plusInt 176 192) Операции и функции отличаются только формой записи. Следующие выражения эквивалентны: 3 + 8 и (+) 3 827 `div` 4 и div 27 47 `plusInt` 11 и plusInt 7 11 Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Определение функций с помощью уравнений Уравнения задают правила, по которым происходит вычисление функции, то есть каким образом результат получается из аргументов функции, например: plusInt :: Int -> Int -> IntplusInt a b = a + b divMod :: (Int, Int) -> (Int, Int)divMod (a, b) = (a `div` b, a `mod` b) Уравнения могут содержать условные выражения и рекурсивные обращения, например: factorial :: Integer -> Integerfactorial n = if n == 0 then 1 else n * (factorial (n-1)) sum :: Integer -> Integersum n = n + if n == 0 then 0 else sum (n-1) Уравнений для одной функции может быть несколько, тогда аргументы последовательно сопоставляются с образцами: factorial1 :: Integer -> Integerfactorial1 n | n == 0 = 1 | n > 0 = n * (factorial1 (n-1)) factorial2 :: Integer -> Integerfactorial2 0 = 1 factorial2 n = n * (factorial2 (n-1)) Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Подготовка и запуск программ В лекциях всюду используется «краткий» синтаксис, при котором каждое новое определениеначинается с новой строки. Этот синтаксис очень чувствителен к расположению строк.Каждое новое «предложение» должно начинаться ровно в той же позиции, что и предыдущее. Если требуется разместить «предложение» на нескольких строках, то «продолжения»должны начинаться с отступом от позиции предыдущей строки factorial :: Integer -> Integer factorial n | n == 0 = 1 | n > 0 = n * (factorial (n-1)) - ошибка factorial :: Integer -> Integer factorial n | n == 0 = 1 | n > 0 = n * (factorial (n-1)) - ошибка Заметьте, что второе уравнение, в котором опущены имя функции и образец, синтаксическиявляется продолжением первого уравнения. factorial :: Integer -> Integer factorial n | n == 0 = 1 | n > 0 = n *factorial (n-1)) - нет ошибки Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Подготовка и запуск программ «Полный» синтаксис подразумевает, что последовательность «предложений» заключается в фигурные скобки, а отдельные предложения отделяются друг от друга точками с запятой. В случае использования «полного» синтаксиса соблюдать отступы не обязательно. { factorial :: Integer -> Integer; factorial n | n == 0 = 1 | n > 0 = n * (factorial (n-1)) } - нет ошибки В программе можно смешивать полный и краткий синтаксис. Анализатор переключается между ними по следующим правилам: • анализ начинается в режиме полного синтаксиса; • если там, где нужна открывающая фигурная скобка, ее нет, то она автоматически вставляется, а анализатор переходит в режим краткого синтаксиса; • если в режиме краткого синтаксиса очередная строка начинается с той же позиции, что и начало всего предложения, то перед ней автоматически вставляется точка с запятой; • если очередная строка начинается с отступом влево от начала текущего предложения, то вставляется закрывающая фигурная скобка; • если очередная строка начинается с отступом вправо, то это – продолжение предыдущей строки (ничего не вставляется) Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Исполнение программ с помощью текстовой подстановки factorial :: Integer -> Integer factorial n | n == 0 = 1 | n > 0 = n * (factorial (n-1)) factorial 3 3 * (factorial (3-1)) 3 * (factorial 2) 3 * (2 * (factorial (2-1))) 3 * (2 * (factorial 1)) 3 * (2 * (1 * (factorial (1-1)))) 3 * (2 * (1 * (factorial 0))) 3 * (2 * (1 * 1)) 6 Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Несколько определений простых арифметических функций -- Вычисление наибольшего общего делителя двух натуральных чисел gcd :: Integer -> Integer -> Integer gcd m n | m < n = gcd n m | n < 0 = error "gcd: Wrong argument" gcd m 0 = m gcd m n = gcd n (m `mod` n) -- Проверка заданного натурального числа на простоту prime :: Integer -> Bool prime' :: Integer -> Integer -> Bool prime p | p <= 0 = error "prime: Non-positive argument" | otherwise = prime' 2 p prime' d p | d * d > p = True | p `mod` d == 0 = False | otherwise = prime' (d+1) p Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.
Немного о явных и неявных преобразованиях типов Язык Haskell – строго типизированный. Это означает, что во время компиляции тип любого выражения известен и контролируется. Тем не менее, многие функции и операции допускают в качестве аргументов (операндов) значения разных типов. 2 + 5 – сложение целых; 2.5 + 3.5 – сложение вещественных; 2 + 3.5 – сложение вещественных (первый операнд перед операцией преобразуется); n + 3.5 where n = 2– сложение вещественных (тип первого операнда «выводится» как вещественное); n + 3.5 where { n :: Int; n = 2 }– ошибка (тип первого операнда явно указан как целый); fromIntegraln + 3.5 where { n :: Int; n = 2 }– сложение вещественных (тип первого операнда явно преобразован); a + b where { a :: Int; a = 2; b :: Integer; b = 12 }– ошибка (операнды имеют разные типы); a + fromIntegral b where { a :: Int; a = 2; b :: Integer; b = 12 }– сложение коротких целых; • Некоторые функции преобразования типов: • fromIntegral, fromRational, fromEnum, • truncate, round, ceiling, floor Кубенский А.А. Функциональное программирование. Глава 1. Элементы функционального программирования.