Cool Haskell In Action
Влад и Женя
Специально для DMLabs

Санкт-Петербург
17 ноября 2013
О чем пойдет речь
●
●
●
●
●
●
●

Краеугольные камни ФП
Быстрый интро
Основные фишки и приемы в Haskell
Сравнение с другими языками ФП + и Красивые примеры, элегантный код
Функциональный подход к проектированию систем
Зачем стоит изучать Haskell
Краеугольные Камни ФП
● Функции Высших Порядков. Мы в мире функций!
● Рекурсия. Нет понятия цикл
● Чистые функции (детерминированность, отсутствие
побочных эффектов)
● Модель вычислений без состояний (нет
присваивания => нет переменных)
Сильные и слабые стороны ФП
● Нет необходимости отслеживать побочные эффекты
=> надежность кода
● Нет нужды проверять внешнее состояние программы
=> простота модульного тестирования
● Возможность автоматического распараллеливания
● Оптимизация при компиляции
● Слабое быстродействие (за счет порождения новых
данных). Нужен эффективный сборщик мусора
● Нелепица при вводе-выводе
Основы Синтаксиса
●
●
●
●

Типы данных - Integer, Int, Bool, Char, Float, Double
Сравнения - < <= > >= == /=
Логические операции - && || not
Функции - нет ни запятых, ни скобок

Пример:
1) sqr x = x*x
●

Конструкция if then else

●

Рекурсия основной строительный блок

fact 1 = 1
fact n = fact (n-1) * n

Пример:
abs x = if x > 0
then x
else –x
Накапливающийся параметр
fact n = fact’ n 1
fact' 1 p = p
fact' n p = fact’ (n-1) (n*p)

fact' 5 1 = fact’ 4 (5*1)
fact' 4 5 = fact’ 3 (4*5)
fact’ 3 20 = fact’ 2 (3*20)
fact’ 2 60 = fact’ 1 (2*60)
fact’ 1 120 =120

51
45
3 20
2 60
1 120
Списки
●
●
●
●

[] - пустой список
[1,2,3,4] или [1..n] - элементы должны быть одного типа
Оператор : - приписать элемент в начало. 1: [2,3] => [1,2,3]
++ - конкатенация списков. [1,2] ++ [3,4] => [1,2,3,4]

Функции для работы со списками:
●
●
●
●
●
●

head [1,2,3,4] => 1
tail [1,2,3,4] => [2,3,4]
length [1,2,3,4] => 4
zip [1,2] [3,4] => [(1,3), (1,4), (2,3), (3,4)] + fst и snd
last [1,2,3,4] => 4 - очень медленно, не рекомендуется
init [1,2,3,4] => [1,2,3]
Pattern Matching 1
●

Идея в том, чтобы описывать преобразования над данными, а не
точную последовательность действий

Примеры:
1) fact n = fact’ n 1
fact' 1 p = p
fact' n p = fact’ (n-1) (n*p)
2) sum [] = 0
sum xs = head xs + sum(tail xs)
Задачки
●
●
●
●

gen_list 5 => [5,4,3,2,1]
mylength [1,2,3,4] => 4
rev [1,2,3,4] => [4,3,2,1]
rev используя накапливающиеся параметры
Решения
1) gen_list 0 = []
gen_list n = n : f (n-1)
2) mylength [] = 0
mylength xs = 1 + length (tail xs)
3) rev1 [] = []
rev1 xs = rev2 (tail xs) ++ [x]
rev2 [] = []
rev3 xs = last xs : rev(init xs)
Решения
4) rev xs = rev’ xs []
rev’ [] ys = ys
rev‘ xs ys = rev’ (tail xs) ((head x) : ys)
Pattern Matching 2
sum [] = 0
sum (x:xs) = x + sum xs
Могут быть сложными:
min_in_list [] = 1/0
min_in_list [x] = x
min_in_list (x:y:xs) = if x < y then min_in_list(x:xs) else min_in_list(y:xs)
Лямбда-выражения и
Каррирование
●
●
●

По простому лямбда-выражения - это функции без имени
В Haskell x -> x*x
Каррирование - сопоставление функции от многих переменных
функцию берущую свои аргументы по одному

Пример:
mysum x y z = x + y + z ⇔ mysum2 = x -> y -> z -> x + y + z
В чем плюс?
mysum42 = mysum2 42
Основные функции высших
порядков
●
●
●

map func xs - map (x -> 10*x +5) [1,2,3,4] => [15, 25, 35, 45]
filter cond xs - filter (x -> x > 0) [-1,1,-3,4] => [1,4]
foldr и foldl - свертка

Пример:
sum xs = foldr (+) 0 xs
product xs = foldr (*) 1 xs
Понимание foldr
foldr f e xs
Как бы:
res = e
для каждого x - элемента цикла (справа налево) {
res = f x res
}
Пример:
foldr (x res -> if x>0 then x+res else res) 0 [1,-3,-7,4,9] => 14
Задачки
●
●
●
●
●

myzip
mymap
myfoldr
операция and для списка через foldr
prod_mod3 [1,2,3,4,5,6] => 3*6 = 18
Решения
1) myzip [] _ = []
myzip _ [] = []
myzip (x:xs) (y:ys) = (x,y) : myzip xs ys
Note: map ((x,y) -> x+y) [(1,2), (3,4), (5,6)] => [3, 7, 11]
2) mymap func [] = []
mymap func (x:xs) = func x : mymap func xs
Решения
3) myfoldr f e [] = e
myfoldr f e (x:xs) = f e (myfoldr f e xs)
myfoldr f e (x:xs) = e `f` (myfoldr f e xs)
4) and xs = foldr (&&) True xs
5) prod_mod3 xs = foldr (x y -> if mod x 3 == 0 then x*y else y) 1 xs
Data с вариантами
data Point = Pt Integer Integer
●

Для доступа к полям - pattern matching

dist (Pt x y) = sqrt (x*x + y*y)
data Person = Student String Integer Integer | Professor String String
[Student “Иванов” 2 541, Professor “Моль” “Геометрия”]
data Tree = Empty | Node Integer Tree Tree
Node 1 (Node 2 Empty Empty) (Node 3 Empty Empty)
sumTree Empty = 0
sumTree (Node val l r) = val + sumTree l + sumTree r
List Comprehension
Хотим делать что-то вроде S = { x: x <- R, x > 5 }
Пожалуйста - [sin x | x <- [1..n]] => [sin 1, sin 2, … , sin n]
Более сложно:
identity n = [ [ if i == j then 1 else 0 i <- [1..n] | j <- [1..n] ]
Сравнение Haskell с другими ФП
Все языки ФП имеют теже преимущества, что и Haskell, НО:
●
●

Для преодоления нелепицы при вводе-выводе остальные ФП
используют хаки и становятся не чисто функциональными
Haskell же использует монады и остается чистым, а значит сохраняет
все преимущества (надежность, распараллеливаемость, простоту
тестирования) в любых ситуациях

про монады и в следующий раз :)
Pattern matching
Задаются строчки z и y.
Можно ли получить строку z из строки y путем
выбрасывания символов?
(строка = массив символов)
Pattern matching
Можно ли получить строку z из строки y путем
выбрасывания символов?
func [] [] = True
func [] (ys) = True
func (zs) [] = False
Pattern matching
Можно ли получить строку z из строки y путем
выбрасывания символов?
func [] [] = True
func [] (ys) = True
func (zs) [] = False
func (z:zs) (y:ys)
| z==y

= func zs ys

| otherwise

= func (z:zs) ys
Крутокод на Си
int func(char* y, char* z) {
while (*y) {
if (!(*z)) {
return false;
}
if (*y==*z) {
y++;
};
z++;
}
return true;
}
Pattern Matching
quicksort [] = []
quicksort (p:xs) =
(quicksort lesser) ++ [p] ++ (quicksort greater)
where
lesser

= filter (< p) xs

greater = filter (>= p) xs
Cool quicksort
qsort [] = []
qsort (x:xs) = qsort [t | x<-xs, t<x]
++ [x]
++ qsort [t | x<-xs, t>x]
Интерпретатор скриптового языка
Написать интерпретатор, который принимает список
операций над стеком, и возвращает содержимое стека
после выполнения операций:
PUSH 1
PUSH 5
POP
= > [1]
Интерпретатор скриптового языка
func "PUSH" (a:[]) (xs) = (a:xs)
func "POP" ([]) (x:xs) = (xs)
func _ _ x = x
Интерпретатор скриптового языка
func "PUSH" (a:[]) (xs) = (a:xs)
func "POP" ([]) (x:xs) = (xs)
func _ _ x = x
data CommandInfo = Command String [Int]
process [] stack = stack
process (x:xs) stack = process xs (apply x)
where
apply (Command cname cargs) = func cname cargs stack
execute xs = process xs []
Интерпретатор скриптового языка
execute [
Command
Command
Command
Command
]
[2,1]

"PUSH" [1],
"PUSH" [2],
"PUSH" [3],
"POP" []
Интерпретатор скриптового языка
Дописать команду ADD, которая будет прибавлять
число-аргумент к вершине стека.
func "PUSH" (a:[]) (xs) = (a:xs)
func "POP" ([]) (x:xs) = (xs)
func _ _ x = x
Интерпретатор скриптового языка
func "PUSH" (a:[]) (xs) = (a:xs)
func "POP" ([]) (x:xs) = (xs)
func "ADD" (a:[]) (x:xs) = ((a+x):xs)
func _ _ x = x
Интерпретатор скриптового языка
Написать интерпретатор, который принимает список
операций над стеком.
PUSH -> добавляет в стек; ничего не выводит
SUM -> выводит сумму
POP -> выводит удаленный элемент
CLS -> Очищает вывод
Интерпретатор скриптового языка
PUSH 1

[1]

[]

PUSH 2

[1, 2]

[]

PUSH 5

[1, 2, 5]

[]

PUSH 3

[1, 2, 5, 3]

[]

POP

[1, 2, 5]

[3]

CLS

[1, 2, 5]

[]

POP

[1, 2]

[5]

SUM

[1, 2]

[5, 3]
Интерпретатор скриптового языка
Интерпретатор №1:
Стек -> (функция) -> Стек’
Интерпретатор №2:
(Стек+Вывод) -> (функция) -> (Стек+Вывод)’
Состояние вычисления = Стек+Вывод
data Result = State [Int] [Int]
Интерпретатор скриптового языка
data Result = State [Int] [Int]
func "PUSH" (a:[]) (State stack out) = State (a:stack) out
func "POP" [] (State (x:stack) out) = State stack (out++
[x])
func "SUM" [] (State stack out) = State stack (out++[sum
stack])
func "CLS" [] (State stack out) = State stack []
func _ _ x = x
Интерпретатор скриптового языка
data Result = State [Int] [Int]
...
data CommandInfo = Command String [Int]
process [] (State stack out) = out
process (x:xs) state = process xs (apply x)
where
apply (Command cname cargs) = func cname cargs state
execute xs = process xs (State [] [])
Язык  подход
Ф-подход:
данные -> (функция) -> данные’
ОО-подход:
объект = данные + функции
Сравнение подходов
Задает ли язык программирования ограничение на
парадигму?
Сравнение подходов
class Vector2 { public: float x, y;

void Add(Vector2 &other) {
x += other.x;
y += other.y;
}

VS

Vector2 Add2(Vector2 &other) {
Vector2 result;
result.x = x + other.x;
result.y = y + other.y;
return result;
}
Применимость подходов
Ф-подход:
Программа ориентирована на обработку (алгоритмы шифрования,
эвалюаторы)
Легко изменяется функционал
ОО-подход:
Программа ориентирована на данные (прикладные программы)
Легко изменяются данные/источники
Дополнительная задача №1
Есть фигуры: эллипс, квадрат, прямоугольник
1. Построить иерархию классов
2. Написать функцию, которая увеличивает ширину фигуры в x раз
(площадь тоже увеличивиается в x раз)
Shape
Ellipse

Rectangle
Square
Дополнительная задача №1
void enlarge(float coef)
vs
Shape enlarge(float coef)
Дополнительная задача №2
void myownhugedataset::operation() {
try {
a = b;
c *= 2;
...
} catch(exception e) {
printf(":(");
//Как восстановить рабочее состояние объекта?
}
}
Дополнительная задача №2
myownhugedataset myownhugedataset::operation() {
myownhugedataset copy = *this;
try {
copy = func1(copy);
...
} catch(exception e) {
return *this;
}
return copy;
}
Зачем стоит изучать Haskell

Лекция о языке программирования Haskell

  • 1.
    Cool Haskell InAction Влад и Женя Специально для DMLabs Санкт-Петербург 17 ноября 2013
  • 2.
    О чем пойдетречь ● ● ● ● ● ● ● Краеугольные камни ФП Быстрый интро Основные фишки и приемы в Haskell Сравнение с другими языками ФП + и Красивые примеры, элегантный код Функциональный подход к проектированию систем Зачем стоит изучать Haskell
  • 3.
    Краеугольные Камни ФП ●Функции Высших Порядков. Мы в мире функций! ● Рекурсия. Нет понятия цикл ● Чистые функции (детерминированность, отсутствие побочных эффектов) ● Модель вычислений без состояний (нет присваивания => нет переменных)
  • 4.
    Сильные и слабыестороны ФП ● Нет необходимости отслеживать побочные эффекты => надежность кода ● Нет нужды проверять внешнее состояние программы => простота модульного тестирования ● Возможность автоматического распараллеливания ● Оптимизация при компиляции ● Слабое быстродействие (за счет порождения новых данных). Нужен эффективный сборщик мусора ● Нелепица при вводе-выводе
  • 5.
    Основы Синтаксиса ● ● ● ● Типы данных- Integer, Int, Bool, Char, Float, Double Сравнения - < <= > >= == /= Логические операции - && || not Функции - нет ни запятых, ни скобок Пример: 1) sqr x = x*x ● Конструкция if then else ● Рекурсия основной строительный блок fact 1 = 1 fact n = fact (n-1) * n Пример: abs x = if x > 0 then x else –x
  • 6.
    Накапливающийся параметр fact n= fact’ n 1 fact' 1 p = p fact' n p = fact’ (n-1) (n*p) fact' 5 1 = fact’ 4 (5*1) fact' 4 5 = fact’ 3 (4*5) fact’ 3 20 = fact’ 2 (3*20) fact’ 2 60 = fact’ 1 (2*60) fact’ 1 120 =120 51 45 3 20 2 60 1 120
  • 7.
    Списки ● ● ● ● [] - пустойсписок [1,2,3,4] или [1..n] - элементы должны быть одного типа Оператор : - приписать элемент в начало. 1: [2,3] => [1,2,3] ++ - конкатенация списков. [1,2] ++ [3,4] => [1,2,3,4] Функции для работы со списками: ● ● ● ● ● ● head [1,2,3,4] => 1 tail [1,2,3,4] => [2,3,4] length [1,2,3,4] => 4 zip [1,2] [3,4] => [(1,3), (1,4), (2,3), (3,4)] + fst и snd last [1,2,3,4] => 4 - очень медленно, не рекомендуется init [1,2,3,4] => [1,2,3]
  • 8.
    Pattern Matching 1 ● Идеяв том, чтобы описывать преобразования над данными, а не точную последовательность действий Примеры: 1) fact n = fact’ n 1 fact' 1 p = p fact' n p = fact’ (n-1) (n*p) 2) sum [] = 0 sum xs = head xs + sum(tail xs)
  • 9.
    Задачки ● ● ● ● gen_list 5 =>[5,4,3,2,1] mylength [1,2,3,4] => 4 rev [1,2,3,4] => [4,3,2,1] rev используя накапливающиеся параметры
  • 10.
    Решения 1) gen_list 0= [] gen_list n = n : f (n-1) 2) mylength [] = 0 mylength xs = 1 + length (tail xs) 3) rev1 [] = [] rev1 xs = rev2 (tail xs) ++ [x] rev2 [] = [] rev3 xs = last xs : rev(init xs)
  • 11.
    Решения 4) rev xs= rev’ xs [] rev’ [] ys = ys rev‘ xs ys = rev’ (tail xs) ((head x) : ys)
  • 12.
    Pattern Matching 2 sum[] = 0 sum (x:xs) = x + sum xs Могут быть сложными: min_in_list [] = 1/0 min_in_list [x] = x min_in_list (x:y:xs) = if x < y then min_in_list(x:xs) else min_in_list(y:xs)
  • 13.
    Лямбда-выражения и Каррирование ● ● ● По простомулямбда-выражения - это функции без имени В Haskell x -> x*x Каррирование - сопоставление функции от многих переменных функцию берущую свои аргументы по одному Пример: mysum x y z = x + y + z ⇔ mysum2 = x -> y -> z -> x + y + z В чем плюс? mysum42 = mysum2 42
  • 14.
    Основные функции высших порядков ● ● ● mapfunc xs - map (x -> 10*x +5) [1,2,3,4] => [15, 25, 35, 45] filter cond xs - filter (x -> x > 0) [-1,1,-3,4] => [1,4] foldr и foldl - свертка Пример: sum xs = foldr (+) 0 xs product xs = foldr (*) 1 xs
  • 15.
    Понимание foldr foldr fe xs Как бы: res = e для каждого x - элемента цикла (справа налево) { res = f x res } Пример: foldr (x res -> if x>0 then x+res else res) 0 [1,-3,-7,4,9] => 14
  • 16.
    Задачки ● ● ● ● ● myzip mymap myfoldr операция and длясписка через foldr prod_mod3 [1,2,3,4,5,6] => 3*6 = 18
  • 17.
    Решения 1) myzip []_ = [] myzip _ [] = [] myzip (x:xs) (y:ys) = (x,y) : myzip xs ys Note: map ((x,y) -> x+y) [(1,2), (3,4), (5,6)] => [3, 7, 11] 2) mymap func [] = [] mymap func (x:xs) = func x : mymap func xs
  • 18.
    Решения 3) myfoldr fe [] = e myfoldr f e (x:xs) = f e (myfoldr f e xs) myfoldr f e (x:xs) = e `f` (myfoldr f e xs) 4) and xs = foldr (&&) True xs 5) prod_mod3 xs = foldr (x y -> if mod x 3 == 0 then x*y else y) 1 xs
  • 19.
    Data с вариантами dataPoint = Pt Integer Integer ● Для доступа к полям - pattern matching dist (Pt x y) = sqrt (x*x + y*y) data Person = Student String Integer Integer | Professor String String [Student “Иванов” 2 541, Professor “Моль” “Геометрия”] data Tree = Empty | Node Integer Tree Tree Node 1 (Node 2 Empty Empty) (Node 3 Empty Empty) sumTree Empty = 0 sumTree (Node val l r) = val + sumTree l + sumTree r
  • 20.
    List Comprehension Хотим делатьчто-то вроде S = { x: x <- R, x > 5 } Пожалуйста - [sin x | x <- [1..n]] => [sin 1, sin 2, … , sin n] Более сложно: identity n = [ [ if i == j then 1 else 0 i <- [1..n] | j <- [1..n] ]
  • 21.
    Сравнение Haskell сдругими ФП Все языки ФП имеют теже преимущества, что и Haskell, НО: ● ● Для преодоления нелепицы при вводе-выводе остальные ФП используют хаки и становятся не чисто функциональными Haskell же использует монады и остается чистым, а значит сохраняет все преимущества (надежность, распараллеливаемость, простоту тестирования) в любых ситуациях про монады и в следующий раз :)
  • 22.
    Pattern matching Задаются строчкиz и y. Можно ли получить строку z из строки y путем выбрасывания символов? (строка = массив символов)
  • 23.
    Pattern matching Можно липолучить строку z из строки y путем выбрасывания символов? func [] [] = True func [] (ys) = True func (zs) [] = False
  • 24.
    Pattern matching Можно липолучить строку z из строки y путем выбрасывания символов? func [] [] = True func [] (ys) = True func (zs) [] = False func (z:zs) (y:ys) | z==y = func zs ys | otherwise = func (z:zs) ys
  • 25.
    Крутокод на Си intfunc(char* y, char* z) { while (*y) { if (!(*z)) { return false; } if (*y==*z) { y++; }; z++; } return true; }
  • 26.
    Pattern Matching quicksort []= [] quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater) where lesser = filter (< p) xs greater = filter (>= p) xs
  • 27.
    Cool quicksort qsort []= [] qsort (x:xs) = qsort [t | x<-xs, t<x] ++ [x] ++ qsort [t | x<-xs, t>x]
  • 28.
    Интерпретатор скриптового языка Написатьинтерпретатор, который принимает список операций над стеком, и возвращает содержимое стека после выполнения операций: PUSH 1 PUSH 5 POP = > [1]
  • 29.
    Интерпретатор скриптового языка func"PUSH" (a:[]) (xs) = (a:xs) func "POP" ([]) (x:xs) = (xs) func _ _ x = x
  • 30.
    Интерпретатор скриптового языка func"PUSH" (a:[]) (xs) = (a:xs) func "POP" ([]) (x:xs) = (xs) func _ _ x = x data CommandInfo = Command String [Int] process [] stack = stack process (x:xs) stack = process xs (apply x) where apply (Command cname cargs) = func cname cargs stack execute xs = process xs []
  • 31.
    Интерпретатор скриптового языка execute[ Command Command Command Command ] [2,1] "PUSH" [1], "PUSH" [2], "PUSH" [3], "POP" []
  • 32.
    Интерпретатор скриптового языка Дописатькоманду ADD, которая будет прибавлять число-аргумент к вершине стека. func "PUSH" (a:[]) (xs) = (a:xs) func "POP" ([]) (x:xs) = (xs) func _ _ x = x
  • 33.
    Интерпретатор скриптового языка func"PUSH" (a:[]) (xs) = (a:xs) func "POP" ([]) (x:xs) = (xs) func "ADD" (a:[]) (x:xs) = ((a+x):xs) func _ _ x = x
  • 34.
    Интерпретатор скриптового языка Написатьинтерпретатор, который принимает список операций над стеком. PUSH -> добавляет в стек; ничего не выводит SUM -> выводит сумму POP -> выводит удаленный элемент CLS -> Очищает вывод
  • 35.
    Интерпретатор скриптового языка PUSH1 [1] [] PUSH 2 [1, 2] [] PUSH 5 [1, 2, 5] [] PUSH 3 [1, 2, 5, 3] [] POP [1, 2, 5] [3] CLS [1, 2, 5] [] POP [1, 2] [5] SUM [1, 2] [5, 3]
  • 36.
    Интерпретатор скриптового языка Интерпретатор№1: Стек -> (функция) -> Стек’ Интерпретатор №2: (Стек+Вывод) -> (функция) -> (Стек+Вывод)’ Состояние вычисления = Стек+Вывод data Result = State [Int] [Int]
  • 37.
    Интерпретатор скриптового языка dataResult = State [Int] [Int] func "PUSH" (a:[]) (State stack out) = State (a:stack) out func "POP" [] (State (x:stack) out) = State stack (out++ [x]) func "SUM" [] (State stack out) = State stack (out++[sum stack]) func "CLS" [] (State stack out) = State stack [] func _ _ x = x
  • 38.
    Интерпретатор скриптового языка dataResult = State [Int] [Int] ... data CommandInfo = Command String [Int] process [] (State stack out) = out process (x:xs) state = process xs (apply x) where apply (Command cname cargs) = func cname cargs state execute xs = process xs (State [] [])
  • 39.
    Язык подход Ф-подход: данные-> (функция) -> данные’ ОО-подход: объект = данные + функции
  • 40.
    Сравнение подходов Задает лиязык программирования ограничение на парадигму?
  • 41.
    Сравнение подходов class Vector2{ public: float x, y; void Add(Vector2 &other) { x += other.x; y += other.y; } VS Vector2 Add2(Vector2 &other) { Vector2 result; result.x = x + other.x; result.y = y + other.y; return result; }
  • 42.
    Применимость подходов Ф-подход: Программа ориентированана обработку (алгоритмы шифрования, эвалюаторы) Легко изменяется функционал ОО-подход: Программа ориентирована на данные (прикладные программы) Легко изменяются данные/источники
  • 43.
    Дополнительная задача №1 Естьфигуры: эллипс, квадрат, прямоугольник 1. Построить иерархию классов 2. Написать функцию, которая увеличивает ширину фигуры в x раз (площадь тоже увеличивиается в x раз) Shape Ellipse Rectangle Square
  • 44.
    Дополнительная задача №1 voidenlarge(float coef) vs Shape enlarge(float coef)
  • 45.
    Дополнительная задача №2 voidmyownhugedataset::operation() { try { a = b; c *= 2; ... } catch(exception e) { printf(":("); //Как восстановить рабочее состояние объекта? } }
  • 46.
    Дополнительная задача №2 myownhugedatasetmyownhugedataset::operation() { myownhugedataset copy = *this; try { copy = func1(copy); ... } catch(exception e) { return *this; } return copy; }
  • 47.