SlideShare uma empresa Scribd logo
1 de 47
Baixar para ler offline
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) 
● Функционально-декларативный дизайн 
● Максимум смысла, минимум кода 
● https://github.com/graninas/Amber
Ограничения 
● Нет классов, только struct (POD) 
● Списки инициализации! 
● Нет циклов for(;;), есть for_each() 
● Всяческие eDSLs 
● Иммутабельность 
● Лямбды, функции, замыкания
Структура Амбера 
Полюс 
Амбера Средние миры 
Полюс 
Хаоса 
Амбер 
Бергма 
Кашфа 
Авалон 
Земля 
Дворы 
Хаоса
Координаты тени, игрока 
Полюс 
Амбера 
Амбер 
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 Расстояние до Хаоса
Коородинаты тени, игрока 
namespace Element { 
enum ElementType { 
Air, 
Sky, 
Water, 
Ground 
}; 
} 
Амбер 
Бергма 
Кашфа 
Авалон 
Игрок: 
90 Земля 
10 Вода 
100 Воздух 
70 Небо 
typedef std::map<Element::ElementType, int> ShadowStructure;
Декларативное задание координат 
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) 
}; 
}
Определение тени 
typedef std::function<ShadowStructure(ShadowStructure, 
Direction::DirectionType)> 
ShadowVariator; 
struct Shadow { 
ShadowName name; 
ShadowVariator variator; 
ShadowStructure structure; 
double influence; 
};
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
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... 
} 
};
Игровой процесс 
Input Output 
Amber 1 
AmberTask 
Evaluator 
AAmmbbeerTrTaasksk AmberTask Amber 2
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; 
};
Список задач 
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); 
}
Выполнение списка задач 
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; 
}
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...
Конструирование лямбд 
AmberTask goDirection(Direction::DirectionType dir) { 
return [dir](const Amber& amber) { 
// Bla-bla with dir 
}; 
} 
AmberTaskList tasks = { 
goDirection(Direction::North), 
inflateShadowStorms, 
tickWorldTime 
};
Небезопасный код 
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 
Result 2 
safeEvalTask
Комбинаторный 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; 
};
Комбинаторы 
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. 
}
Как может выглядеть 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; 
}
Обобщение безопасного типа 
// Было: 
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; 
};
Обобщение безопасного типа 
// Было: 
struct Artifact { 
Amber amber; 
bool success; 
}; 
// Стало: 
enum MaybeValue { 
Just, 
Nothing 
}; 
template <typename Data> 
struct Maybe { 
Data data; 
MaybeValue mValue; 
};
Обобщение безопасного типа 
// Было: 
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);
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); 
}
Монадические функции в 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 
}; 
}
Использование 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; 
}
Использование 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; 
}
Связывание многих 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; 
}
Простая реализация 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; 
};
Простая реализация 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; 
}
Простая реализация 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; 
}
Тестирование 
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); 
}
Положительные моменты 
● Лямбды - универсальный инструмент дизайна 
● Краткость функционального кода 
● Высокая модульность 
● Прекрасная тестируемость 
● Редуцирована структурная сложность ПО 
● Многие задачи решаются проще, понятнее 
● Больше внимания задаче, а не борьбе с 
языком
Проблемы и особенности 
● Массированное копирование данных 
● Большее потребление памяти 
● Меньшая производительность 
● Опасные замыкания в лямбдах 
● Чистота функций не контролируется 
● Нет алгебраических типов данных 
● Нет каррирования, заменители плохи
C++ User Group, Новосибирск 
Мы это сделали, 
всем спасибо! :) 
Александр Гранин 
graninas@gmail.com
А теперь - бонус!
Проблема: глубокие структуры 
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!"; 
}
Проблема: глубокие структуры 
// 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; 
}
Встречайте: линзы! 
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!" }; 
};
Линза - это геттер + сеттер 
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)); 
}
Геттер и сеттер - это лямбды 
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; 
}); 
}
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; 
};
Зуммирование и фокусировка 
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; 
}
C++ User Group, Новосибирск 
На этот раз 
действительно все! 
Вопросы? 
Александр Гранин 
graninas@gmail.com

Mais conteúdo relacionado

Mais procurados

Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода Pavel Tsukanov
 
Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Sergey Platonov
 
Очередной скучный доклад про логгирование
Очередной скучный доклад про логгированиеОчередной скучный доклад про логгирование
Очередной скучный доклад про логгированиеPython Meetup
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловПолухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловSergey Platonov
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
 
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...Alexey Paznikov
 
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++.   Р...ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++.   Р...
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...Alexey Paznikov
 
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»Platonov Sergey
 
Догнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castДогнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castRoman Orlov
 
Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Sergey Platonov
 

Mais procurados (18)

Zagursky
ZagurskyZagursky
Zagursky
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 
Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++
 
Clojure #2 (2014)
Clojure #2 (2014)Clojure #2 (2014)
Clojure #2 (2014)
 
Очередной скучный доклад про логгирование
Очередной скучный доклад про логгированиеОчередной скучный доклад про логгирование
Очередной скучный доклад про логгирование
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловПолухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
 
Clojure #1
Clojure #1Clojure #1
Clojure #1
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
 
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
 
msumobi2. Лекция 2
msumobi2. Лекция 2msumobi2. Лекция 2
msumobi2. Лекция 2
 
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++.   Р...ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++.   Р...
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
 
Bytecode
BytecodeBytecode
Bytecode
 
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
 
course js day 2
course js day 2course js day 2
course js day 2
 
C sharp deep dive
C sharp deep diveC sharp deep dive
C sharp deep dive
 
Догнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castДогнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_cast
 
JRebel
JRebelJRebel
JRebel
 
Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++
 

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

C#. От основ к эффективному коду
C#. От основ к эффективному кодуC#. От основ к эффективному коду
C#. От основ к эффективному кодуVasiliy Deynega
 
Школа-студия разработки приложений для iOS. Лекция 1. Objective-C
Школа-студия разработки приложений для iOS. Лекция 1. Objective-CШкола-студия разработки приложений для iOS. Лекция 1. Objective-C
Школа-студия разработки приложений для iOS. Лекция 1. Objective-CГлеб Тарасов
 
Groovy presentation.
Groovy presentation.Groovy presentation.
Groovy presentation.Infinity
 
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)Mikhail Kurnosov
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кодаAndrey Karpov
 
Зачем нужна Scala?
Зачем нужна Scala?Зачем нужна Scala?
Зачем нужна Scala?Vasil Remeniuk
 
Ciklum .NET Saturday - Introduction to TypeScript
Ciklum .NET Saturday - Introduction to TypeScriptCiklum .NET Saturday - Introduction to TypeScript
Ciklum .NET Saturday - Introduction to TypeScriptDmytro Mindra
 
NetworkUA - 2012 - Introduction TypeScript
NetworkUA - 2012 - Introduction TypeScript NetworkUA - 2012 - Introduction TypeScript
NetworkUA - 2012 - Introduction TypeScript Dmytro Mindra
 
DevConf. Дмитрий Сошников - ECMAScript 6
DevConf. Дмитрий Сошников - ECMAScript 6DevConf. Дмитрий Сошников - ECMAScript 6
DevConf. Дмитрий Сошников - ECMAScript 6Dmitry Soshnikov
 
Groovy On Grails
Groovy On GrailsGroovy On Grails
Groovy On Grailsguest32215a
 
Ruby - или зачем мне еще один язык программирования?
Ruby - или зачем мне еще один язык программирования?Ruby - или зачем мне еще один язык программирования?
Ruby - или зачем мне еще один язык программирования?Pavel Tsukanov
 
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs ReflectionRoslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs ReflectionDenis Tsvettsih
 
CodeFest 2014. Пугачев С. — Язык TypeScript или JavaScript на стероидах
CodeFest 2014. Пугачев С. — Язык TypeScript или JavaScript на стероидахCodeFest 2014. Пугачев С. — Язык TypeScript или JavaScript на стероидах
CodeFest 2014. Пугачев С. — Язык TypeScript или JavaScript на стероидахCodeFest
 
Lift, play, akka, rails part1
Lift, play, akka, rails part1Lift, play, akka, rails part1
Lift, play, akka, rails part1Eduard Antsupov
 
Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Vasya Petrov
 
Web осень 2012 лекция 9
Web осень 2012 лекция 9Web осень 2012 лекция 9
Web осень 2012 лекция 9Technopark
 

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

C#. От основ к эффективному коду
C#. От основ к эффективному кодуC#. От основ к эффективному коду
C#. От основ к эффективному коду
 
msumobi2. Лекция 1
msumobi2. Лекция 1msumobi2. Лекция 1
msumobi2. Лекция 1
 
Школа-студия разработки приложений для iOS. Лекция 1. Objective-C
Школа-студия разработки приложений для iOS. Лекция 1. Objective-CШкола-студия разработки приложений для iOS. Лекция 1. Objective-C
Школа-студия разработки приложений для iOS. Лекция 1. Objective-C
 
Groovy presentation.
Groovy presentation.Groovy presentation.
Groovy presentation.
 
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
 
Зачем нужна Scala?
Зачем нужна Scala?Зачем нужна Scala?
Зачем нужна Scala?
 
Ciklum .NET Saturday - Introduction to TypeScript
Ciklum .NET Saturday - Introduction to TypeScriptCiklum .NET Saturday - Introduction to TypeScript
Ciklum .NET Saturday - Introduction to TypeScript
 
NetworkUA - 2012 - Introduction TypeScript
NetworkUA - 2012 - Introduction TypeScript NetworkUA - 2012 - Introduction TypeScript
NetworkUA - 2012 - Introduction TypeScript
 
DevConf. Дмитрий Сошников - ECMAScript 6
DevConf. Дмитрий Сошников - ECMAScript 6DevConf. Дмитрий Сошников - ECMAScript 6
DevConf. Дмитрий Сошников - ECMAScript 6
 
Groovy On Grails
Groovy On GrailsGroovy On Grails
Groovy On Grails
 
Intro to Swift techitout
Intro to Swift techitoutIntro to Swift techitout
Intro to Swift techitout
 
Ruby - или зачем мне еще один язык программирования?
Ruby - или зачем мне еще один язык программирования?Ruby - или зачем мне еще один язык программирования?
Ruby - или зачем мне еще один язык программирования?
 
Суперсилы Chrome developer tools
Суперсилы Chrome developer toolsСуперсилы Chrome developer tools
Суперсилы Chrome developer tools
 
Асинхронный JavaScript
Асинхронный JavaScriptАсинхронный JavaScript
Асинхронный JavaScript
 
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs ReflectionRoslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs Reflection
 
CodeFest 2014. Пугачев С. — Язык TypeScript или JavaScript на стероидах
CodeFest 2014. Пугачев С. — Язык TypeScript или JavaScript на стероидахCodeFest 2014. Пугачев С. — Язык TypeScript или JavaScript на стероидах
CodeFest 2014. Пугачев С. — Язык TypeScript или JavaScript на стероидах
 
Lift, play, akka, rails part1
Lift, play, akka, rails part1Lift, play, akka, rails part1
Lift, play, akka, rails part1
 
Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1
 
Web осень 2012 лекция 9
Web осень 2012 лекция 9Web осень 2012 лекция 9
Web осень 2012 лекция 9
 

Mais de Alexander Granin

Concurrent applications with free monads and stm
Concurrent applications with free monads and stmConcurrent applications with free monads and stm
Concurrent applications with free monads and stmAlexander Granin
 
Hierarchical free monads and software design in fp
Hierarchical free monads and software design in fpHierarchical free monads and software design in fp
Hierarchical free monads and software design in fpAlexander Granin
 
Final tagless vs free monad
Final tagless vs free monadFinal tagless vs free monad
Final tagless vs free monadAlexander Granin
 
The present and the future of functional programming in c++
The present and the future of functional programming in c++The present and the future of functional programming in c++
The present and the future of functional programming in c++Alexander Granin
 
О разработке десктопных приложений / About desktop development
О разработке десктопных приложений / About desktop developmentО разработке десктопных приложений / About desktop development
О разработке десктопных приложений / About desktop developmentAlexander Granin
 
Принципы и практики разработки ПО 2 / Principles and practices of software de...
Принципы и практики разработки ПО 2 / Principles and practices of software de...Принципы и практики разработки ПО 2 / Principles and practices of software de...
Принципы и практики разработки ПО 2 / Principles and practices of software de...Alexander Granin
 
Принципы и практики разработки ПО / Principles and practices of software deve...
Принципы и практики разработки ПО / Principles and practices of software deve...Принципы и практики разработки ПО / Principles and practices of software deve...
Принципы и практики разработки ПО / Principles and practices of software deve...Alexander Granin
 
Закон Деметры / Demetra's law
Закон Деметры / Demetra's lawЗакон Деметры / Demetra's law
Закон Деметры / Demetra's lawAlexander Granin
 
Design of big applications in FP
Design of big applications in FPDesign of big applications in FP
Design of big applications in FPAlexander Granin
 
GitHub - зеркало разработчика
GitHub - зеркало разработчикаGitHub - зеркало разработчика
GitHub - зеркало разработчикаAlexander Granin
 
The Present and The Future of Functional Programming in C++
The Present and The Future of Functional Programming in C++The Present and The Future of Functional Programming in C++
The Present and The Future of Functional Programming in C++Alexander Granin
 
Functional programming in C++ LambdaNsk
Functional programming in C++ LambdaNskFunctional programming in C++ LambdaNsk
Functional programming in C++ LambdaNskAlexander Granin
 
Transition graph using free monads and existentials
Transition graph using free monads and existentialsTransition graph using free monads and existentials
Transition graph using free monads and existentialsAlexander Granin
 
Software transactional memory. pure functional approach
Software transactional memory. pure functional approachSoftware transactional memory. pure functional approach
Software transactional memory. pure functional approachAlexander Granin
 
Вы не понимаете ФП / You don't understand FP
Вы не понимаете ФП / You don't understand FPВы не понимаете ФП / You don't understand FP
Вы не понимаете ФП / You don't understand FPAlexander Granin
 
Functional "Life": parallel cellular automata and comonads
Functional "Life": parallel cellular automata and comonadsFunctional "Life": parallel cellular automata and comonads
Functional "Life": parallel cellular automata and comonadsAlexander Granin
 
Functional microscope - Lenses in C++
Functional microscope - Lenses in C++Functional microscope - Lenses in C++
Functional microscope - Lenses in C++Alexander Granin
 
Дизайн больших приложений в ФП
Дизайн больших приложений в ФПДизайн больших приложений в ФП
Дизайн больших приложений в ФПAlexander Granin
 
Линзы - комбинаторная манипуляция данными
Линзы - комбинаторная манипуляция даннымиЛинзы - комбинаторная манипуляция данными
Линзы - комбинаторная манипуляция даннымиAlexander Granin
 

Mais de Alexander Granin (20)

Concurrent applications with free monads and stm
Concurrent applications with free monads and stmConcurrent applications with free monads and stm
Concurrent applications with free monads and stm
 
Hierarchical free monads and software design in fp
Hierarchical free monads and software design in fpHierarchical free monads and software design in fp
Hierarchical free monads and software design in fp
 
Final tagless vs free monad
Final tagless vs free monadFinal tagless vs free monad
Final tagless vs free monad
 
Monadic parsers in C++
Monadic parsers in C++Monadic parsers in C++
Monadic parsers in C++
 
The present and the future of functional programming in c++
The present and the future of functional programming in c++The present and the future of functional programming in c++
The present and the future of functional programming in c++
 
О разработке десктопных приложений / About desktop development
О разработке десктопных приложений / About desktop developmentО разработке десктопных приложений / About desktop development
О разработке десктопных приложений / About desktop development
 
Принципы и практики разработки ПО 2 / Principles and practices of software de...
Принципы и практики разработки ПО 2 / Principles and practices of software de...Принципы и практики разработки ПО 2 / Principles and practices of software de...
Принципы и практики разработки ПО 2 / Principles and practices of software de...
 
Принципы и практики разработки ПО / Principles and practices of software deve...
Принципы и практики разработки ПО / Principles and practices of software deve...Принципы и практики разработки ПО / Principles and practices of software deve...
Принципы и практики разработки ПО / Principles and practices of software deve...
 
Закон Деметры / Demetra's law
Закон Деметры / Demetra's lawЗакон Деметры / Demetra's law
Закон Деметры / Demetra's law
 
Design of big applications in FP
Design of big applications in FPDesign of big applications in FP
Design of big applications in FP
 
GitHub - зеркало разработчика
GitHub - зеркало разработчикаGitHub - зеркало разработчика
GitHub - зеркало разработчика
 
The Present and The Future of Functional Programming in C++
The Present and The Future of Functional Programming in C++The Present and The Future of Functional Programming in C++
The Present and The Future of Functional Programming in C++
 
Functional programming in C++ LambdaNsk
Functional programming in C++ LambdaNskFunctional programming in C++ LambdaNsk
Functional programming in C++ LambdaNsk
 
Transition graph using free monads and existentials
Transition graph using free monads and existentialsTransition graph using free monads and existentials
Transition graph using free monads and existentials
 
Software transactional memory. pure functional approach
Software transactional memory. pure functional approachSoftware transactional memory. pure functional approach
Software transactional memory. pure functional approach
 
Вы не понимаете ФП / You don't understand FP
Вы не понимаете ФП / You don't understand FPВы не понимаете ФП / You don't understand FP
Вы не понимаете ФП / You don't understand FP
 
Functional "Life": parallel cellular automata and comonads
Functional "Life": parallel cellular automata and comonadsFunctional "Life": parallel cellular automata and comonads
Functional "Life": parallel cellular automata and comonads
 
Functional microscope - Lenses in C++
Functional microscope - Lenses in C++Functional microscope - Lenses in C++
Functional microscope - Lenses in C++
 
Дизайн больших приложений в ФП
Дизайн больших приложений в ФПДизайн больших приложений в ФП
Дизайн больших приложений в ФП
 
Линзы - комбинаторная манипуляция данными
Линзы - комбинаторная манипуляция даннымиЛинзы - комбинаторная манипуляция данными
Линзы - комбинаторная манипуляция данными
 

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

  • 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;
  • 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. Определение тени 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
  • 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. Список задач 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. Выполнение списка задач 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. 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. Конструирование лямбд AmberTask goDirection(Direction::DirectionType dir) { return [dir](const Amber& amber) { // Bla-bla with dir }; } AmberTaskList tasks = { goDirection(Direction::North), inflateShadowStorms, tickWorldTime };
  • 19. Небезопасный код const AmberTask inflateShadowStorms = [](const Amber& amber) { throw std::runtime_error("Shit happened! :)"); };
  • 20. Данные + результат onSuccess Task Amber 1 Result 1 Amber 2 if (Result 1 == Success) + - onFail Task safeEvalTask Result 2 safeEvalTask
  • 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. Комбинаторы 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. Как может выглядеть 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. Обобщение безопасного типа // Было: 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. Обобщение безопасного типа // Было: struct Artifact { Amber amber; bool success; }; // Стало: enum MaybeValue { Just, Nothing }; template <typename Data> struct Maybe { Data data; MaybeValue mValue; };
  • 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. 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. Монадические функции в 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. Использование 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. Использование 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. Связывание многих 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. Простая реализация 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. Простая реализация 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. Простая реализация 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. Тестирование 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. Положительные моменты ● Лямбды - универсальный инструмент дизайна ● Краткость функционального кода ● Высокая модульность ● Прекрасная тестируемость ● Редуцирована структурная сложность ПО ● Многие задачи решаются проще, понятнее ● Больше внимания задаче, а не борьбе с языком
  • 37. Проблемы и особенности ● Массированное копирование данных ● Большее потребление памяти ● Меньшая производительность ● Опасные замыкания в лямбдах ● Чистота функций не контролируется ● Нет алгебраических типов данных ● Нет каррирования, заменители плохи
  • 38. C++ User Group, Новосибирск Мы это сделали, всем спасибо! :) Александр Гранин graninas@gmail.com
  • 39. А теперь - бонус!
  • 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!" }; };
  • 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. Геттер и сеттер - это лямбды 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. 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. Зуммирование и фокусировка 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. C++ User Group, Новосибирск На этот раз действительно все! Вопросы? Александр Гранин graninas@gmail.com