Доклад о дизайне кода в функциональном стиле на C++, представленный вниманию плюсовиков на C++ User Group Novosibirsk 2014.
В качестве демонстрационного проекта была реализована игра "Амбер" по мотивам "Хроник Амбера" Р. Желязны.
https://github.com/graninas/Amber
1. C++ User Group, Новосибирск
Функционально-декларативный
дизайн на С++
Александр Гранин
graninas@gmail.com
2. О себе
● C++ → Haskell
● Рассказывал на DevDay@2GIS о Haskell
● Рассказывал на TechTalks@NSU о ФП
● Статьи на Хабре о дизайне в ФП
● Работаю в “Лаборатории Касперского”
3. struct Presentation
{
Описание задачи
Функциональная комбинаторика
Продвинутый дизайн
Тестирование
Проблемы и особенности
};
4. О задаче
● Игра “Амбер” (По мотивам “Хроник Амбера” Роджера Желязны)
● C++11 (Qt C++ 5.2.1, gcc 4.8.2)
● Функционально-декларативный дизайн
● Максимум смысла, минимум кода
● https://github.com/graninas/Amber
5. Ограничения
● Нет классов, только struct (POD)
● Списки инициализации!
● Нет циклов for(;;), есть for_each()
● Всяческие eDSLs
● Иммутабельность
● Лямбды, функции, замыкания
6. Структура Амбера
Полюс
Амбера Средние миры
Полюс
Хаоса
Амбер
Бергма
Кашфа
Авалон
Земля
Дворы
Хаоса
7. Координаты тени, игрока
Полюс
Амбера
Амбер
G(90) W(10) A(100) S(70)
Бергма
Кашфа
Авалон
G(30) W(70) A(80) S(90)
Амбер:
90 Земля (G)
10 Вода (W)
100 Воздух (A)
70 Небо (S)
100 Флора
100 Фауна
0 Расстояние до Амбера
100 Расстояние до Хаоса
8. Коородинаты тени, игрока
namespace Element {
enum ElementType {
Air,
Sky,
Water,
Ground
};
}
Амбер
Бергма
Кашфа
Авалон
Игрок:
90 Земля
10 Вода
100 Воздух
70 Небо
typedef std::map<Element::ElementType, int> ShadowStructure;
10. Определение тени
typedef std::function<ShadowStructure(ShadowStructure,
Direction::DirectionType)>
ShadowVariator;
struct Shadow {
ShadowName name;
ShadowVariator variator;
ShadowStructure structure;
double influence;
};
11. ShadowVariator координат игрока
typedef std::function<ShadowStructure(ShadowStructure,
Direction::DirectionType)>
ShadowVariator;
const ShadowVariator identityVariator =
[](const ShadowStructure& structure,
Direction::DirectionType)
{
return structure;
};
N
Ground
S
Water
E
Sky
W
Air
12. ShadowVariator координат игрока
const ShadowVariator bergmaVariator =
[](const ShadowStructure& structure,
Direction::DirectionType dir) -> ShadowStructure {
switch (dir) {
case Direction::North:
return changeElements({ element::Water(-2)
, element::Ground(2) }
, structure);
// and so on for the different directions...
}
};
13. Игровой процесс
Input Output
Amber 1
AmberTask
Evaluator
AAmmbbeerTrTaasksk AmberTask Amber 2
30. Использование Maybe с auto
MaybeAmber goDirectionBinded(const Amber& amber,
Direction::DirectionType dir)
{
auto m1 = just(amber);
auto m2 = bind(m1, lookupVariator);
auto m3 = bind(m2, applyVariator(amber, dir));
auto m4 = bind(m3, updateNearestPlace);
return m4;
}
36. Положительные моменты
● Лямбды - универсальный инструмент дизайна
● Краткость функционального кода
● Высокая модульность
● Прекрасная тестируемость
● Редуцирована структурная сложность ПО
● Многие задачи решаются проще, понятнее
● Больше внимания задаче, а не борьбе с
языком
37. Проблемы и особенности
● Массированное копирование данных
● Большее потребление памяти
● Меньшая производительность
● Опасные замыкания в лямбдах
● Чистота функций не контролируется
● Нет алгебраических типов данных
● Нет каррирования, заменители плохи
38. C++ User Group, Новосибирск
Мы это сделали,
всем спасибо! :)
Александр Гранин
graninas@gmail.com
40. Проблема: глубокие структуры
struct C {
int intC;
std::string stringC;
};
int intC;
std::string stringC;
C
B
A
struct B {
C c;
};
struct A {
B b;
};
// Not Ok: a mutable code
void changeC(A& a) {
a.b.c.intC += 20;
a.b.c.stringC = "Hello, World!";
}
41. Проблема: глубокие структуры
// Immutable code, but still not Ok: too deep structure diving
A changeC(const A& oldA) {
C newC = oldA.b.c;
newC.intC = oldA.b.c.intC + 20;
newC.stringC = "Hello, world!";
int intC;
std::string stringC;
C
B
A
B newB = oldA.b;
newB.c = newC;
A newA = oldA;
newA.b = newB;
return newA;
}
42. Встречайте: линзы!
A changeC(const A &oldA) {
LensStack<A, B, C> stack = zoom(aToBLens(), bToCLens());
A newA = evalLens(stack, oldA, modifyC);
return newA;
}
std::function<C(C)> modifyC = [](const C& c)
{
return C { c.intC + 20, "Hello, World!" };
};