В статически типизированных императивных (мультипарадигменных) языках программирования обычно есть несколько областей видимости сущностей языка (назовем их контекстами):
— глобальный контекст, содержащий глобальные переменные и функции
— локальный — локальные переменные
— контекст класса — члены данные и методы класса, который активен в момент выполнения метода
— контекст замыкания лямбда функции
Возникла идея обобщить это неявное понятие контекста и вынести возможность управления контекстами непосредственно в язык.
Расcмотрим случаи, когда это может понадобиться. Примеры особенно актуальны для языка С++
1. Аллокаторы. Стоит задача работы с большим количеством различных сложных объектов (объекты связаны друг с другом и содержат в себе различные контейнеры), находящихся в динамической памяти.
При этом объекты постоянно создаются и удаляются во время работы программы. Такая задача возникает, например, при разработке движка браузера.
Обычно, функциональность, отвечающая за аллокацию и освобождение памяти для объектов, жестко зашита в коде и поменять схему работы выделения/освобождения памяти очень сложно в большом проекте.
Аллокаторы заданы глобально. Это либо могут быть перегруженные операторы new/delete у каждого класса, либо глобальные функции allocate/deallocate.
Такая задача, как использование разных аллокаторов для различных групп объектов, определяемых в runtime, практически не реализуема — прийдется в каждую функцию в коде добавить ещё один параметр — объект аллокатора.
2. Запись лога и ввод/вывод. Как и в предыдущем случае не понятно что делать с функциями и объектами, отвечающими за ввод/вывод и ведение лога.
Обычно программисту предоставляется две альтернативы — либо делать их глобальными объектами, но тогда потеряется гибкость, либо добавлять в каждую функцию объекты Logger и IO, что потребует написание boilerplate кода.
Для решения данных задач предлагается увеличить выразительность языка программирования путем введения контекстов.
Тогда, всю сквозную функциональность можно добавлять в контексты и при необходимости запускать один и тот же код в разных контекстах.
Вот как это могло бы выглядеть в C++
struct Logger
{
void writeMessage(std::string const &);
Logger(...) {...}
};
template<class IStream, class OStream> struct IO
{
IStream & in;
OStream & out;
IO(...) {...}
};
template<class Allocator> struct Memory
{
void * operator new (std::size_t size) throw (std::bad_alloc);
void operator delete (void s* ptr) throw ();
};
template<class IStream, class OStream, Allocator> [IO<IStream, OStream>, Memory<Allocator>]
{
[Logger] void bar(char b)
{
int * pInt = new int;
out << "Int object created. \n";
writeMessage("Int object created. \n");
}
struct Graph
{
struct Node
{
};
readGraph()
{
...
... = new Node(...); //Pool allocator new
...
}
~Graph()
{
...
delete ptr;
...
}
};
void foo(int a)
{
[Logger("logFile")] bar(a);
//OR
[Logger("logFile")]
{
writeMessage("hello. \n");
bar(a);
}
[Memory<PoolAllocator>()]
{
Graph g;
g.readGraph();
}
//OR
[Memory<PoolAllocator>()] Graph g;
//Создается объект, у которого перед вызовом
//каждой функции и деструктора будет происходить смена контекста
//Memory на Memory<PoolAllocator>()
g.readGraph();
auto readGraphMethod = g.readGraph; //замыкание, содержащее контекст и указатель на метод
}
}
void main()
{
[IO<std::istream, std::ostream>(cin, cout), Memory<SimpleAllocator>()] foo(123);
[IO<std::ifstream, std::ofstream>(std::ifstream("inFile.txt"), std::ofstream("outFile.txt")),
Memory<FastAllocator>()] foo(456);
}
От возможности определения глобальных переменных и функций при наличии контекстов можно будет избавиться.
Как вам такая идея расширения языка? Может в каких-нибудь языках это уже реализовано?
Здравствуйте, Aleх, Вы писали: A> [Logger("logFile")] A> { A> writeMessage("hello. \n"); A> bar(a); A> }
В чём отличие от:
Logger.writeMessage("hello. \n");
Logger.bar(a);
?
Между тем,что я думаю,тем,что я хочу сказать,тем,что я,как мне кажется,говорю,и тем,что вы хотите услышать,тем,что как вам кажется,вы слышите,тем,что вы понимаете,стоит десять вариантов возникновения непонимания.Но всё-таки давайте попробуем...(Э.Уэллс)
Здравствуйте, Aleх, Вы писали:
A>В статически типизированных императивных (мультипарадигменных) языках программирования обычно есть несколько областей видимости сущностей языка (назовем их контекстами): A>- глобальный контекст, содержащий глобальные переменные и функции A>- локальный — локальные переменные A>- контекст класса — члены данные и методы класса, который активен в момент выполнения метода A>- контекст замыкания лямбда функции
Вы забыли еще контекст модуля.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, os24ever, Вы писали:
A>>Как вам такая идея расширения языка? Может в каких-нибудь языках это уже реализовано?
O>Замыкания в JavaScript?
Да, я знаю, что в JavaScript можно похожие вещи делать. Поэтому я в начале писал про статически типизированные языки.
O>И вот недавнее обсуждение
.
L>Передать в конструктор — не вариант?
Не вариант, тк:
— придется передавать через множество функций (которые поптом передадут в конструктор), а это значит, что в каждой функции нужен ещё дополнительный параметр
— если таких объектов много (десятки — сотни тысяч), то указатель на логгер (а обычно нужен не только логгер, но и много чего другого) будет храниться в каждом объекте, что вызовет сильный оверхед по памяти.
A>template<class IStream, class OStream, Allocator> [IO<IStream, OStream>, Memory<Allocator>, Logger]
A>{
A> void f1(char b)
A> {
A> writeMessage("Message. \n");
A> f2(b);
A> f3(b);
A> }
A> void f2(char b)
A> {
A> writeMessage("Message. \n");
A> f4(b);
A> f5(b);
A> }
A> void f3(char b)
A> {
A> writeMessage("Message. \n");
A> f6(b);
A> f7(b);
A> }
A> void f4(char b)
A> {
A> int * pInt = new int;
A> out << "Int object created. \n";
A> writeMessage("Message. \n");
A> }
A> void f5(char b)
A> {
A> int * pDouble = new double;
A> out << "Double object created. \n";
A> writeMessage("Message. \n");
A> }
A> void f6(char b)
A> {
A> int * pFloat = new float;
A> out << "Float object created. \n";
A> writeMessage("Message. \n");
A> }
A> void f7(char b)
A> {
A> int * pChar = new char;
A> out << "Char object created. \n";
A> writeMessage("Message. \n");
A> }
A>}
A>
Это как-бы объединение нескольких областей видимости имён в одну, типа множественного наследования но только не с классами а с объектами.
Например вместо:
class C1 {
def foo() = {}
new C2(this)
}
class C2 (val c1:C1) {
c1.foo()
}
Можно писать так:
class C1 {
def foo() = {}
new C2(this)
}
class C2 [C1] {
foo()
}
Я правильно понял?
Между тем,что я думаю,тем,что я хочу сказать,тем,что я,как мне кажется,говорю,и тем,что вы хотите услышать,тем,что как вам кажется,вы слышите,тем,что вы понимаете,стоит десять вариантов возникновения непонимания.Но всё-таки давайте попробуем...(Э.Уэллс)
Здравствуйте, AlexCab, Вы писали:
AC>Это как-бы объединение нескольких областей видимости имён в одну, типа множественного наследования но только не с классами а с объектами.
Да, в общем это так. При множественном наследовании классы, а вместе с ними и создаваемые родительские объекты, зафиксированы в определении класса, а тут предлагается задавать объекты, чьи области видимости сольются в одну в момент создания объекта. То есть если рассматривать это в терминах множественного наследования, то создаваемому объекту можно передать в качестве родительских уже давно созданные объекты.
AC>Например вместо: AC>
AC>>Это как-бы объединение нескольких областей видимости имён в одну, типа множественного наследования но только не с классами а с объектами. A>Да, в общем это так. При множественном наследовании классы, а вместе с ними и создаваемые родительские объекты, зафиксированы в определении класса, а тут предлагается задавать объекты, чьи области видимости сольются в одну в момент создания объекта. То есть если рассматривать это в терминах множественного наследования, то создаваемому объекту можно передать в качестве родительских уже давно созданные объекты.
Прикольно, хоть и не очень хорошо с точки зрения "интуитивной понятности"(выглядит как обращение к собственным членам класса).
Между тем,что я думаю,тем,что я хочу сказать,тем,что я,как мне кажется,говорю,и тем,что вы хотите услышать,тем,что как вам кажется,вы слышите,тем,что вы понимаете,стоит десять вариантов возникновения непонимания.Но всё-таки давайте попробуем...(Э.Уэллс)
В С++ возникнет проблема с линковкой такого... Тут только динамические
языки могут что то предложить.
Но чисто теоретически, первый шаг на этом направлении уже сделан.
Фактически класс — это вручную созданный контекст, т.к. неявно в каждый
метод передаётся this-указатель на контекст.
Проблема только в том, что контексты классов нельзя каскадировать, т.е.
при создании класса всегда берётся в качестве предка глобальный контекст.
Если бы при создании объекта можно было бы указать объект-предок,
контекст которого был бы виден во всех методах вновь созданного класса..
Здравствуйте, Aleх, Вы писали:
A> Как вам такая идея расширения языка? Может в каких-нибудь языках это уже реализовано?
Вроде типичный IoC, но только с поддержкой на уровне языка, т.к. С++ не поддерживает рефлексии. На всяких java давно подобное сделано в виде IoC-containers, благодаря рефлексии и аннотациям. Да в общем-то можно и на C++ писать в таком стиле, если правильно дизайнить, используя всякие шаблоны типа фабрик, делегатов, етс. Хоть и получается много boilerplate-кода, зато compile-time проверок больше будет.
Ещё плохо, что в твоём примере, скажем, используется неявный пул, хз откуда берущийся в строчке "new Node(...); //Pool allocator new", особенно интересно в случая рекурсий и многопоточного программирования, это же типичная глобальная переменная "текущего" пула.
Здравствуйте, avp_, Вы писали:
_>В С++ возникнет проблема с линковкой такого... Тут только динамические _>языки могут что то предложить.
С линковкой проблема может возникнуть только в случае шаблонных контекстов. И то, там сложности такие же как в реализации export template.
Можно всё это двумя способами реализовывать:
1. Передавать в каждую функцию указатель на контекст.
2. Завести отдельный глобальный стек в который добавлять указатель на контекст (или сам объект контекста) при его изменении и соответственно удалять при выходе из контекста. Такая реализация будет немного экономнее, тк в стеки будет запись не на каждый вызов функции, а только при смене контекста.
Контекст будет представлять собой кортеж/структуру из указателей на составные части (например Memory, Logger, IO).
_>Но чисто теоретически, первый шаг на этом направлении уже сделан. _>Фактически класс — это вручную созданный контекст, т.к. неявно в каждый _>метод передаётся this-указатель на контекст. _>Проблема только в том, что контексты классов нельзя каскадировать, т.е. _>при создании класса всегда берётся в качестве предка глобальный контекст. _>Если бы при создании объекта можно было бы указать объект-предок, _>контекст которого был бы виден во всех методах вновь созданного класса..
Здравствуйте, ., Вы писали:
.>Здравствуйте, Aleх, Вы писали:
A>> Как вам такая идея расширения языка? Может в каких-нибудь языках это уже реализовано? .>Вроде типичный IoC, но только с поддержкой на уровне языка, т.к. С++ не поддерживает рефлексии. На всяких java давно подобное сделано в виде IoC-containers, благодаря рефлексии и аннотациям. Да в общем-то можно и на C++ писать в таком стиле, если правильно дизайнить, используя всякие шаблоны типа фабрик, делегатов, етс. Хоть и получается много boilerplate-кода, зато compile-time проверок больше будет.
Ну вот, поддержка на уровне языка нужна, чтобы не было boilerplate-кода, а так, да, можно всё это и сейчас делать, только неудобно.
.>Ещё плохо, что в твоём примере, скажем, используется неявный пул, хз откуда берущийся в строчке "new Node(...); //Pool allocator new", особенно интересно в случая рекурсий и многопоточного программирования, это же типичная глобальная переменная "текущего" пула.
Пул берется отсюда:
void foo(int a)
{
...
[Memory<PoolAllocator>()] Graph g;
...
}
При выходе из функции foo пул разрушается.
Рекурсия пулу никак не помешает. Единственное — в зависимости от потребностей, его нужно будет создать либо один на всё дерево рекурсии, либо при спуске на каждом уровне вниз создавать и разрушать ври выходе из функции.
При многопоточном программировании, в каждом потоке будет свой контекст. Вот
Здравствуйте, Aleх, Вы писали:
.>>Вроде типичный IoC, но только с поддержкой на уровне языка, т.к. С++ не поддерживает рефлексии. На всяких java давно подобное сделано в виде IoC-containers, благодаря рефлексии и аннотациям. Да в общем-то можно и на C++ писать в таком стиле, если правильно дизайнить, используя всякие шаблоны типа фабрик, делегатов, етс. Хоть и получается много boilerplate-кода, зато compile-time проверок больше будет. A>Ну вот, поддержка на уровне языка нужна, чтобы не было boilerplate-кода, а так, да, можно всё это и сейчас делать, только неудобно.
В java от языка достаточно рефлексии, остальное делается на уровне библиотек. Вроде вот есть попытки и для С++ сделать, хотя я не понял, делается ли property injection, без него скучно...
.>>Ещё плохо, что в твоём примере, скажем, используется неявный пул, хз откуда берущийся в строчке "new Node(...); //Pool allocator new", особенно интересно в случая рекурсий и многопоточного программирования, это же типичная глобальная переменная "текущего" пула. A>Пул берется отсюда:
A>При выходе из функции foo пул разрушается.
Да я понял, просто неявная глобальная (вероятно thread local) переменная. А если в контексте 2 пула понадобится?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Aleх wrote:
> С линковкой проблема может возникнуть только в случае шаблонных > контекстов. И то, там сложности такие же как в реализации export > template.
Если какой то класс пишет внутри метода
Logger.Write("...");
То тип переменной Logger будет неясен в нестабильном контексте.
Соответственно obj файл с этим классом нельзя будет использовать в
сценариях динамического контеста.
Да и просто определение переменнной
foo bar;
требует знания декларации типа foo.
Здравствуйте, Aleх, Вы писали:
A>Возникла идея обобщить это неявное понятие контекста и вынести возможность управления контекстами непосредственно в язык.
Не нужно обобщать контекст, нужно работать с зависимостями и связыванием. Для контекстов слишком много сценариев что бы всовывать их все в язык, а вот с зависимостями и связыванием гораздо проще.
Здравствуйте, AlexCab, Вы писали:
AC>>>Это как-бы объединение нескольких областей видимости имён в одну, типа множественного наследования но только не с классами а с объектами. A>>Да, в общем это так. При множественном наследовании классы, а вместе с ними и создаваемые родительские объекты, зафиксированы в определении класса, а тут предлагается задавать объекты, чьи области видимости сольются в одну в момент создания объекта. То есть если рассматривать это в терминах множественного наследования, то создаваемому объекту можно передать в качестве родительских уже давно созданные объекты. AC>Прикольно, хоть и не очень хорошо с точки зрения "интуитивной понятности"(выглядит как обращение к собственным членам класса).
Похоже на попытку улучшить синтаксис Dependency Injection.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.