О курсе: зачем он, как устроен и что в нём будет, глава за главой
Вспоминаем ФП: глава 1 конспекта по разделам, с задачами
термы и редукция · типы · функции, данные, классы типов и монады в Haskell
О курсе
Фольклор, который никто не рассказывает
Многие сложные вещи в дизайне языков и программ — частные случаи нескольких простых принципов
Эти принципы считаются «общеизвестным фольклором», поэтому их не объясняют
− Знание рассеяно по книгам, статьям и культовым блог-постам; собрать картину самому — долго
⇒Цель курса — собрать фольклор в одном месте и организовать в систему
⇒ Курс явно опирается на классические работы; чтение статей — часть работы в курсе
Что здесь значит «функциональное»
ΔФП — трепетное отношение к понятию эффекта
в других школах мысли эффект — аксиоматическая данность
в ФП — предмет изучения, сознательного конструирования и аккуратного обращения
⇒ Это полезно для изучения языков и построения выразительных конструкций
⇒ И это основа продуктивного стиля программирования
i Функциональные языки сравнительно просты, поэтому новые идеи часто рождаются в них и расходятся дальше
Haskell как язык курса: воплощает многие концепции, доведённые до логического завершения, достаточно выразителен, чтобы кодировать остальные, и при этом промышленный
Широта против глубины
Курс широкий, но не всегда глубокий
детали GHC и теоретико-категорные основания — в общем виде
языковой дизайн через оптику ФП — максимально подробно
Курс для вас, если вы
интересуетесь дизайном языков и красивыми обобщениями
Редукция: аргумент подставлен во все свободные вхождения y в теле, вершины @ и \lambda исчезли. Мы вернулись к исходному выражению.
x ÷ (10 × 4+ 2)
÷
x
+
×
2
10
4
Правило редукции
Δβ-редукция — единственное правило переписывания: найти применение \lambda-функции к аргументу и подставить аргумент вместо связанной переменной
(\lambda x.\,M)\ N \ \rightsquigarrow_\beta\ [x \mapsto N]\ M
⋯
@
λx
N
⋯
[x \mapsto N]\ M
Задача: редукция
Найдите редексы и доведите терм до нормальной формы, считая + и числа примитивами: (\lambda f\,x.\,f\ (f\ x))\ (\lambda y.\,y + 1)\ 0. Сколько понадобилось β-шагов?
i Метки чисто синтаксические, но их можно интерпретировать: самая популярная интерпретация — тип как множество, int \to int как множество функций \mathbb{Z} \to \mathbb{Z}
Дерево вывода по кликам
Проверим, что \lambda f\,x.\,f\ x имеет тип (\sigma \to \tau) \to \sigma \to \tau. Здесь \Gamma = \{f : \sigma \to \tau,\ x : \sigma\}.
В Haskell это flip ($), или (&) из Data.Function: :t \x y -> y x даёт a -> (a -> b) -> b.
Функции в Haskell
Haskell — расширенное типизированное λ-исчисление
Плюс примитивные типы, объявления имён, структуры данных и классы типов
ghci> (\x -> x +1) 45ghci>:t \x -> x +1
\x -> x +1 ::Int->Int-- на самом деле числа полиморфны, но об этом позже
Функциям дают имена; сигнатуры на верхнем уровне пишут явно
f ::Int->Intf x = x +1
Δ Имя типа с маленькой буквы — типовая переменная: параметрический полиморфизм. id ничего не знает про аргумент, но результат обязан быть того же типа
id :: a -> aid x = x
Функции высших порядков, операторы, рекурсия
Δ Функции принимают функции — функции высших порядков
Имя из специальных символов — оператор, применяется инфиксно
($) :: (a -> b) -> a -> bf $ x = f x
Рекурсия с охранными выражениями для базового случая
factorial ::Int->Intfactorial n| n <1=1|otherwise= n * factorial (n -1)
Что выведет ghci> :t uncurry (flip const)?
Что выведет ghci> :t first . first при first :: (a -> a') -> (a, b) -> (a', b)?
uncurry (flipconst) :: (a, c) -> cfirst . first :: (a -> a') -> ((a, b1), b2) -> ((a', b1), b2)
Аккумулирующий параметр
Реализуйте факториал с помощью техники аккумулирующего параметра.
factorial ::Int->Intfactorial = go 1where go acc n| n <1= acc|otherwise= go (acc * n) (n -1)
Данные в Haskell
Типы данных и паттерн-матчинг
dataAnimal=CatStringInt|DogString
ΔCat и Dog — конструкторы данных: функции, реализованные языком; выделяют память и позиционно размещают компоненты
Cat ::String->Int->AnimalDog ::String->Animal
Чтобы воспользоваться данными, значение деконструируют паттерн-матчингом: образец выбирается, если похож на то, как значение сконструировано, и его переменные начинают ссылаться на компоненты
describeAnimal ::Animal->StringdescribeAnimal animal =case animal ofCat name nLifes ->"This is cat "++ name ++" "++show nLifesDog name ->"This is dog "++ name
Записи с именованными полями порождают функции-аксессоры: getName :: Penguin -> String
Определите кайнд конструктора типа data Free f a = Pure a | Free (f (Free f a)).
Free :: (* -> *) -> * -> *: f применяется к типу, значит f :: * -> *; a стоит под Pure, значит a :: *.
Синонимы и обёртки
Синонимы типов — просто другое имя для типа, если запись оригинала слишком длинная
typeT a =VeryLongTypeInt (a ->AnotherLongType a)
newtype — тип с одним конструктором и одним полем: не нужно выделять память под тег и ссылки на поля, значение обёртки — значение оборачиваемого типа, а сама обёртка существует только во время компиляции
Классы типов: один идентификатор, разные реализации
Δ Параметрический полиморфизм — один код для разных типов; классы типов — один идентификатор для разных реализаций, как перегрузка: механизм специального (ad-hoc) полиморфизма
classEq a where (==) :: a -> a ->Bool
instanceEqCourseIdwhereCourseId x ==CourseId y = x == yinstanceEq a =>Eq [a] where [] == [] =True x:xs == y:ys = x == y && xs == ys _ == _ =False
Реализация выбирается по типу аргументов в месте вызова
Параметрически-полиморфная функция ничего не может делать с аргументом, кроме как вернуть его или передать дальше
Чтобы делать что-то ещё, нужна дополнительная информация о типе: suc n = n + 1 не работает для строк, поэтому тип suc :: a -> a некорректен
⇒ Классы типов — способ ограничить полиморфизм: не любой тип, а любой, для которого есть нужные операции
suc ::Num a => a -> asuc n = n +1
Задачи про классы типов
Реализуйте функцию, проверяющую равенство всех элементов данного списка.
Реализуйте инстанс полугруппы для функций.
Реализуйте проверку равенства функций.
allEqual ::Eq a => [a] ->BoolallEqual xs =and (zipWith (==) xs (drop1 xs))instanceSemigroup b =>Semigroup (a -> b) where-- уже есть в base (f <> g) x = f x <> g xinstance (Enum a, Bounded a, Eq b) =>Eq (a -> b) where-- только для конечных областей f == g =all (\x -> f x == g x) [minBound..maxBound]
Монады в Haskell
Функтор
ΔFunctor объявляется для конструкторов типов и заменяет все элементы контейнера, не меняя его структуру
classFunctor (f ::*->*) where fmap :: (a -> b) -> f a -> f b
instanceFunctor [] where fmap :: (a -> b) -> [a] -> [b]fmap _ [] = []fmap f (x:xs) = f x :fmap f xs
Эффекты как структура над результатом
В Haskell функция просто вычисляет результат. Но часто нужно и «что-то ещё»: изменить состояние, написать в консоль — побочные эффекты
Вернуть можно только результат, поэтому эффекты кодируем как дополнительную структуру, оборачивающую чистый результат: вместо a функция возвращает тип вычисленийf a
Эффект
f
функция может завершиться ошибкой
Maybe
читает глобальное окружение e
e -> _
читает и обновляет состояние s
s -> (s, _)
Стандартная библиотека даёт классы типов для значений вида f a: они позволяют работать с a внутри, как будто структуры f нет
Аппликативный функтор и монада
Applicative позволяет писать выражения над вычислениями f a
classFunctor f =>Applicative (f ::*->*) where pure :: a -> f a liftA2 :: (a -> b -> c) -> f a -> f b -> f cinstanceApplicativeMaybewherepure=Just liftA2 _ Nothing _ =Nothing liftA2 _ _ Nothing=Nothing liftA2 f (Just x) (Just y) =Just (f x y)
Monad позволяет последовательно компоновать вычисления в императивном стиле
classApplicative m =>Monad (m ::*->*) where (>>=) :: m a -> (a -> m b) -> m bnewtypeState s a =State { runState :: s -> (s, a) }
instanceMonad (State s) where m >>= k =State \s ->let (s', x) = runState m s in runState (k x) s'
Состояние в императивном стиле
get ::State s sget =State \s -> (s, s)put :: s ->State s ()put newS =State \_ -> (newS, ())example ::StateIntIntexample = get >>= \x -> put 42>>= \() -> get >>= \y ->pure (x + y)
Трасса runState example 1:
get при s = 1 даёт (1, 1): x = 1
put 42 даёт (42, ())
get при s = 42 даёт (42, 42): y = 42
pure (x + y) даёт (42, 43)
ghci> runState example 1(42,43)
Для таких цепочек есть do-нотация: тот же example строчка в строчку
example =do x <- get put 42 y <- getpure (x + y)
Задачи про монады
Реализуйте liftA3 через liftA2.
Реализуйте >>= через join и наоборот.
Считайте два числа с консоли, поделите одно на другое нацело и распечатайте результат; если остаток ненулевой, распечатайте и его.
liftA3 f a b c = liftA2 ($) (liftA2 f a b) cm >>= k = join (fmap k m)join mm = mm >>=idmain =do a <- readLn ::IOInt b <-readLnlet (q, r) = a `divMod` bprint q when (r /=0) $print r
Что мы вспомнили
термы
программа — дерево; вычисление — β-редукция, подстановка аргумента вместо параметра
типы
метки на вершинах по правилам; система типов — синтаксис меток и дроби; метка как множество
функции
\lambda, сигнатуры, типовые переменные, функции высших порядков, операторы
данные
конструкторы как функции, паттерн-матчинг, абстракция в типе, кайнды, newtype
классы
один идентификатор — разные реализации; ограничение полиморфизма
монады
эффект — структура над результатом; Functor, Applicative, Monad, State, do
Домашка fp2-01: интерпретатор Lalalang — изучить выданный код, добавить операции, S-выражения, паттерн-матчинг. Минимум — задачи 1.1–1.2
На следующем занятии: параметрический полиморфизм — глава 2
Материалы
Что почитать
Конспект курса, глава 1 «Воспоминания о ФП» и Введение