Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
C++ User Group, Новосибирск 
Функционально-декларативный 
дизайн на С++ 
Александр Гранин 
graninas@gmail.com
О себе 
● C++ → Haskell 
● Рассказывал на DevDay@2GIS о Haskell 
● Рассказывал на TechTalks@NSU о ФП 
● Статьи на Хабре о ...
struct Presentation 
{ 
Описание задачи 
Функциональная комбинаторика 
Продвинутый дизайн 
Тестирование 
Проблемы и особен...
О задаче 
● Игра “Амбер” (По мотивам “Хроник Амбера” Роджера Желязны) 
● C++11 (Qt C++ 5.2.1, gcc 4.8.2) 
● Функционально-...
Ограничения 
● Нет классов, только struct (POD) 
● Списки инициализации! 
● Нет циклов for(;;), есть for_each() 
● Всяческ...
Структура Амбера 
Полюс 
Амбера Средние миры 
Полюс 
Хаоса 
Амбер 
Бергма 
Кашфа 
Авалон 
Земля 
Дворы 
Хаоса
Координаты тени, игрока 
Полюс 
Амбера 
Амбер 
G(90) W(10) A(100) S(70) 
Бергма 
Кашфа 
Авалон 
G(30) W(70) A(80) S(90) 
А...
Коородинаты тени, игрока 
namespace Element { 
enum ElementType { 
Air, 
Sky, 
Water, 
Ground 
}; 
} 
Амбер 
Бергма 
Кашфа...
Декларативное задание координат 
ShadowStructure::value_type Air(int air) { 
return ShadowStructure::value_type(Element::A...
Определение тени 
typedef std::function<ShadowStructure(ShadowStructure, 
Direction::DirectionType)> 
ShadowVariator; 
str...
ShadowVariator координат игрока 
typedef std::function<ShadowStructure(ShadowStructure, 
Direction::DirectionType)> 
Shado...
ShadowVariator координат игрока 
const ShadowVariator bergmaVariator = 
[](const ShadowStructure& structure, 
Direction::D...
Игровой процесс 
Input Output 
Amber 1 
AmberTask 
Evaluator 
AAmmbbeerTrTaasksk AmberTask Amber 2
AmberTask - задачи на лямбдах 
typedef std::function<Amber (const Amber&)> AmberTask; 
const AmberTask goNorth = [](const ...
Список задач 
typedef std::function<Amber (const Amber&)> AmberTask; 
typedef std::list<AmberTask> AmberTaskList; 
Amber g...
Выполнение списка задач 
typedef std::function<Amber (const Amber&)> AmberTask; 
typedef std::list<AmberTask> AmberTaskLis...
Boilerplate 
const AmberTask goNorth = [](const Amber& amber) { 
// Bla-bla with Direction::North 
}; 
const AmberTask goS...
Конструирование лямбд 
AmberTask goDirection(Direction::DirectionType dir) { 
return [dir](const Amber& amber) { 
// Bla-b...
Небезопасный код 
const AmberTask inflateShadowStorms = [](const Amber& amber) 
{ 
throw std::runtime_error("Shit happened...
Данные + результат onSuccess 
Task 
Amber 1 
Result 1 
Amber 2 
if (Result 1 
== Success) 
+ 
- 
onFail Task safeEvalTask ...
Комбинаторный eDSL 
const AmberTask tickOneAmberHour = [](const Amber& amber) { 
auto action1Res = anyway (inflateShadowSt...
Комбинаторы 
struct Artifact { 
Amber amber; 
bool success; 
}; 
Artifact wrap(const Amber& amber) 
{ 
Artifact artifact {...
Как может выглядеть eval() 
Artifact eval(const AmberTask& task, const Artifact& artifact) { 
Artifact newArtifact = artif...
Обобщение безопасного типа 
// Было: 
struct Artifact { 
Amber amber; 
bool success; 
}; 
AmberTask goNorthTask = [](const...
Обобщение безопасного типа 
// Было: 
struct Artifact { 
Amber amber; 
bool success; 
}; 
// Стало: 
enum MaybeValue { 
Ju...
Обобщение безопасного типа 
// Было: 
Artifact wrap(const Amber& amber); 
Artifact onSuccess(const AmberTask& task, const ...
Maybe - это монада 
template <typename Input, typename Output> Maybe<Output> 
bind(const Maybe<Input>& input, 
const std::...
Монадические функции в Maybe 
const std::function<Maybe<ShadowVariator>(Amber)> 
lookupVariator = [](const Amber& amber) {...
Использование Maybe 
MaybeAmber goDirectionBinded(const Amber& amber, 
Direction::DirectionType dir) { 
MaybeAmber mbAmber...
Использование Maybe с auto 
MaybeAmber goDirectionBinded(const Amber& amber, 
Direction::DirectionType dir) 
{ 
auto m1 = ...
Связывание многих Maybe 
MaybeAmber goDirectionStacked(const Amber& amber, 
Direction::DirectionType dir) { 
MaybeActionSt...
Простая реализация MaybeActionStack 
template <typename M1, typename M2, 
typename M3, typename M4> 
struct MaybeActionSta...
Простая реализация bindMany 
template <typename M1, typename M2, typename M3, typename M4> 
MaybeActionStack<M1, M2, M3, M...
Простая реализация evalMaybes 
template <typename M1, typename M2, typename M3, typename M4> 
Maybe<M4> evalMaybes(const M...
Тестирование 
void Testing::changeElementTest() { 
amber::ShadowStructure structure = { { amber::Element::Ground, 90 
, { ...
Положительные моменты 
● Лямбды - универсальный инструмент дизайна 
● Краткость функционального кода 
● Высокая модульност...
Проблемы и особенности 
● Массированное копирование данных 
● Большее потребление памяти 
● Меньшая производительность 
● ...
C++ User Group, Новосибирск 
Мы это сделали, 
всем спасибо! :) 
Александр Гранин 
graninas@gmail.com
А теперь - бонус!
Проблема: глубокие структуры 
struct C { 
int intC; 
std::string stringC; 
}; 
int intC; 
std::string stringC; 
C 
B 
A 
s...
Проблема: глубокие структуры 
// Immutable code, but still not Ok: too deep structure diving 
A changeC(const A& oldA) { 
...
Встречайте: линзы! 
A changeC(const A &oldA) { 
LensStack<A, B, C> stack = zoom(aToBLens(), bToCLens()); 
A newA = evalLen...
Линза - это геттер + сеттер 
Lens<A, B> aToBLens() { 
return lens<A, B> ( GETTER(A, b) 
, SETTER(A, B, b)); 
} 
Lens<B, C>...
Геттер и сеттер - это лямбды 
Lens<A, B> aToBLensDesugared() { 
return lens<A, B> 
( [](const A& a) { return a.b; } 
, [](...
LensStack - та же идея, что и 
MaybeActionStack 
template <typename Zoomed1, typename Zoomed2, typename 
Zoomed3 = Identit...
Зуммирование и фокусировка 
template <typename Zoomed1, typename Zoomed2, 
typename Zoomed3> 
LensStack<Zoomed1, Zoomed2, ...
C++ User Group, Новосибирск 
На этот раз 
действительно все! 
Вопросы? 
Александр Гранин 
graninas@gmail.com
Upcoming SlideShare
Loading in …5
×

Функционально декларативный дизайн на C++

682 views

Published on

Доклад о дизайне кода в функциональном стиле на C++, представленный вниманию плюсовиков на C++ User Group Novosibirsk 2014.

В качестве демонстрационного проекта была реализована игра "Амбер" по мотивам "Хроник Амбера" Р. Желязны.

https://github.com/graninas/Amber

Published in: Software
  • Be the first to comment

  • Be the first to like this

Функционально декларативный дизайн на C++

  1. 1. C++ User Group, Новосибирск Функционально-декларативный дизайн на С++ Александр Гранин graninas@gmail.com
  2. 2. О себе ● C++ → Haskell ● Рассказывал на DevDay@2GIS о Haskell ● Рассказывал на TechTalks@NSU о ФП ● Статьи на Хабре о дизайне в ФП ● Работаю в “Лаборатории Касперского”
  3. 3. struct Presentation { Описание задачи Функциональная комбинаторика Продвинутый дизайн Тестирование Проблемы и особенности };
  4. 4. О задаче ● Игра “Амбер” (По мотивам “Хроник Амбера” Роджера Желязны) ● C++11 (Qt C++ 5.2.1, gcc 4.8.2) ● Функционально-декларативный дизайн ● Максимум смысла, минимум кода ● https://github.com/graninas/Amber
  5. 5. Ограничения ● Нет классов, только struct (POD) ● Списки инициализации! ● Нет циклов for(;;), есть for_each() ● Всяческие eDSLs ● Иммутабельность ● Лямбды, функции, замыкания
  6. 6. Структура Амбера Полюс Амбера Средние миры Полюс Хаоса Амбер Бергма Кашфа Авалон Земля Дворы Хаоса
  7. 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. 8. Коородинаты тени, игрока namespace Element { enum ElementType { Air, Sky, Water, Ground }; } Амбер Бергма Кашфа Авалон Игрок: 90 Земля 10 Вода 100 Воздух 70 Небо typedef std::map<Element::ElementType, int> ShadowStructure;
  9. 9. Декларативное задание координат ShadowStructure::value_type Air(int air) { return ShadowStructure::value_type(Element::Air, air); } ShadowStructure amberShadowStructure() { return { { Element::Ground, 90 } , { Element::Water, 10 } , Air(100) , Sky(70) }; }
  10. 10. Определение тени typedef std::function<ShadowStructure(ShadowStructure, Direction::DirectionType)> ShadowVariator; struct Shadow { ShadowName name; ShadowVariator variator; ShadowStructure structure; double influence; };
  11. 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. 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. 13. Игровой процесс Input Output Amber 1 AmberTask Evaluator AAmmbbeerTrTaasksk AmberTask Amber 2
  14. 14. AmberTask - задачи на лямбдах typedef std::function<Amber (const Amber&)> AmberTask; const AmberTask goNorth = [](const Amber& amber) { const ShadowVariator variator = // somehow get variator Amber newAmber = amber; newAmber.playerPos = variator(amber.playerPos, Direction::North); return newAmber; };
  15. 15. Список задач typedef std::function<Amber (const Amber&)> AmberTask; typedef std::list<AmberTask> AmberTaskList; Amber goNorth(const Amber& amber) { AmberTaskList tasks = { goNorth, inflateShadowStorms, tickWorldTime }; return evaluate(amber, tasks); }
  16. 16. Выполнение списка задач typedef std::function<Amber (const Amber&)> AmberTask; typedef std::list<AmberTask> AmberTaskList; Amber evaluate(const Amber& amber, const AmberTaskList& tasks) { Amber currentAmber = amber; std::for_each(tasks.begin(), tasks.end(), [&currentAmber](const AmberTask& task) { currentAmber = task(currentAmber); }); return currentAmber; }
  17. 17. Boilerplate const AmberTask goNorth = [](const Amber& amber) { // Bla-bla with Direction::North }; const AmberTask goSouth = [](const Amber& amber) { // The same Bla-bla with Direction::South }; // And so on...
  18. 18. Конструирование лямбд AmberTask goDirection(Direction::DirectionType dir) { return [dir](const Amber& amber) { // Bla-bla with dir }; } AmberTaskList tasks = { goDirection(Direction::North), inflateShadowStorms, tickWorldTime };
  19. 19. Небезопасный код const AmberTask inflateShadowStorms = [](const Amber& amber) { throw std::runtime_error("Shit happened! :)"); };
  20. 20. Данные + результат onSuccess Task Amber 1 Result 1 Amber 2 if (Result 1 == Success) + - onFail Task safeEvalTask Result 2 safeEvalTask
  21. 21. Комбинаторный eDSL const AmberTask tickOneAmberHour = [](const Amber& amber) { auto action1Res = anyway (inflateShadowStorms, wrap(amber)); auto action2Res = onSuccess (affectShadowStorms, action1Res); auto action3Res = onFail (shadowStabilization, action2Res); auto action4Res = anyway (tickWorldTime, action3Res); return action4Res.amber; }; AmberTask goNorthTask = [](const Amber& amber) { auto action1Res = anyway (goNorth, wrap(amber)); auto action2Res = anyway (tickOneAmberHour, action1Res); return action2Res.amber; };
  22. 22. Комбинаторы struct Artifact { Amber amber; bool success; }; Artifact wrap(const Amber& amber) { Artifact artifact { amber, true, {} }; return artifact; } Artifact onSuccess(const AmberTask& task, const Artifact& artifact) { // eval() when artifact.success == true. // otherwise, return an old artifact. } Artifact anyway(const AmberTask& task, const Artifact& artifact) { // no check of previous task success. // just safely eval() a new task. }
  23. 23. Как может выглядеть eval() Artifact eval(const AmberTask& task, const Artifact& artifact) { Artifact newArtifact = artifact; try { Amber newAmber = task(artifact.amber); newArtifact.amber = newAmber; newArtifact.success = true; } catch (const std::exception& e) { // Do something with e newArtifact = artifact; newArtifact.success = false; } return newArtifact; }
  24. 24. Обобщение безопасного типа // Было: struct Artifact { Amber amber; bool success; }; AmberTask goNorthTask = [](const Amber& amber) { Artifact action1Res = onSuccess (amberTask1, wrap(amber)); Artifact action2Res = onSuccess (amberTask2, action1Res); Artifact action3Res = onSuccess (amberTask3, action2Res); return action3Res.amber; };
  25. 25. Обобщение безопасного типа // Было: struct Artifact { Amber amber; bool success; }; // Стало: enum MaybeValue { Just, Nothing }; template <typename Data> struct Maybe { Data data; MaybeValue mValue; };
  26. 26. Обобщение безопасного типа // Было: Artifact wrap(const Amber& amber); Artifact onSuccess(const AmberTask& task, const Artifact& artifact); // Стало: template <typename Input> Maybe<Input> just(const Input& input); template <typename Input, typename Output> Maybe<Output> bind(const Maybe<Input>& input, const std::function<Maybe<Output>(Input)>& action);
  27. 27. Maybe - это монада template <typename Input, typename Output> Maybe<Output> bind(const Maybe<Input>& input, const std::function<Maybe<Output>(Input)>& action) { if (input.mValue == Nothing) { return nothing<Output>(); } return action(input.data); }
  28. 28. Монадические функции в Maybe const std::function<Maybe<ShadowVariator>(Amber)> lookupVariator = [](const Amber& amber) { return ...; // retrieve the nearest shadow's variator }; std::function<Maybe<Amber>(ShadowVariator)> applyVariator(const Amber& amber, Direction::DirectionType dir) { return [&amber, dir](const ShadowVariator& variator) { // apply variator to passed amber, using dir }; }
  29. 29. Использование Maybe MaybeAmber goDirectionBinded(const Amber& amber, Direction::DirectionType dir) { MaybeAmber mbAmber1 = just(amber); MaybeShadowVariator mbVariator = bind(mbAmber1, lookupVariator); MaybeAmber mbAmber2 = bind(mbVariator, applyVariator(amber, dir)); MaybeAmber mbAmber3 = bind(mbAmber2, updateNearestPlace); return mbAmber3; }
  30. 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; }
  31. 31. Связывание многих Maybe MaybeAmber goDirectionStacked(const Amber& amber, Direction::DirectionType dir) { MaybeActionStack<Amber, ShadowVariator, Amber, Amber> stack = bindMany(lookupVariator, applyVariator(amber, dir), updateNearestPlace); MaybeAmber mbAmber = evalMaybes(just(amber), stack); return mbAmber; }
  32. 32. Простая реализация MaybeActionStack template <typename M1, typename M2, typename M3, typename M4> struct MaybeActionStack { std::function<Maybe<M2>(M1)> action1; std::function<Maybe<M3>(M2)> action2; std::function<Maybe<M4>(M3)> action3; };
  33. 33. Простая реализация bindMany template <typename M1, typename M2, typename M3, typename M4> MaybeActionStack<M1, M2, M3, M4> bindMany(const std::function<Maybe<M2>(M1)> action1, const std::function<Maybe<M3>(M2)> action2, const std::function<Maybe<M4>(M3)> action3) { MaybeActionStack<M1, M2, M3, M4> stack; stack.action1 = action1; stack.action2 = action2; stack.action3 = action3; return stack; }
  34. 34. Простая реализация evalMaybes template <typename M1, typename M2, typename M3, typename M4> Maybe<M4> evalMaybes(const Maybe<M1>& m1, const MaybeActionStack<M1, M2, M3, M4>& stack) { Maybe<M2> m2 = bind<M1, M2>(m1, stack.action1); Maybe<M3> m3 = bind<M2, M3>(m2, stack.action2); Maybe<M4> m4 = bind<M3, M4>(m3, stack.action3); return m4; }
  35. 35. Тестирование void Testing::changeElementTest() { amber::ShadowStructure structure = { { amber::Element::Ground, 90 , { amber::Element::Water, 10 } }; amber::ShadowStructure expected = { { amber::Element::Ground, 100 } , { amber::Element::Water, 10 } }; auto newStructure = changeElement(structure, amber::Element::Ground, 10); ASSERT_EQ(expected, newStructure); }
  36. 36. Положительные моменты ● Лямбды - универсальный инструмент дизайна ● Краткость функционального кода ● Высокая модульность ● Прекрасная тестируемость ● Редуцирована структурная сложность ПО ● Многие задачи решаются проще, понятнее ● Больше внимания задаче, а не борьбе с языком
  37. 37. Проблемы и особенности ● Массированное копирование данных ● Большее потребление памяти ● Меньшая производительность ● Опасные замыкания в лямбдах ● Чистота функций не контролируется ● Нет алгебраических типов данных ● Нет каррирования, заменители плохи
  38. 38. C++ User Group, Новосибирск Мы это сделали, всем спасибо! :) Александр Гранин graninas@gmail.com
  39. 39. А теперь - бонус!
  40. 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. 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. 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!" }; };
  43. 43. Линза - это геттер + сеттер Lens<A, B> aToBLens() { return lens<A, B> ( GETTER(A, b) , SETTER(A, B, b)); } Lens<B, C> bToCLens() { return lens<B, C> ( GETTER(B, c) , SETTER(B, C, c)); }
  44. 44. Геттер и сеттер - это лямбды Lens<A, B> aToBLensDesugared() { return lens<A, B> ( [](const A& a) { return a.b; } , [](const A& a, const B& b) { A newA = a; newA.b = b; return newA; }); }
  45. 45. LensStack - та же идея, что и MaybeActionStack template <typename Zoomed1, typename Zoomed2, typename Zoomed3 = Identity, typename Zoomed4 = Identity> struct LensStack { Lens<Zoomed1, Zoomed2> lens1; Lens<Zoomed2, Zoomed3> lens2; Lens<Zoomed3, Zoomed4> lens3; };
  46. 46. Зуммирование и фокусировка template <typename Zoomed1, typename Zoomed2, typename Zoomed3> LensStack<Zoomed1, Zoomed2, Zoomed3, Identity> zoom(Lens<Zoomed1, Zoomed2> l1 , Lens<Zoomed2, Zoomed3> l2) { LensStack<Zoomed1, Zoomed2, Zoomed3, Identity> ls; ls.lens1 = l1; ls.lens2 = l2; ls.lens3 = idL<Zoomed3>(); return ls; }
  47. 47. C++ User Group, Новосибирск На этот раз действительно все! Вопросы? Александр Гранин graninas@gmail.com

×