В статически типизированных императивных (мультипарадигменных) языках программирования обычно есть несколько областей видимости сущностей языка (назовем их контекстами):
— глобальный контекст, содержащий глобальные переменные и функции
— локальный — локальные переменные
— контекст класса — члены данные и методы класса, который активен в момент выполнения метода
— контекст замыкания лямбда функции
Возникла идея обобщить это неявное понятие контекста и вынести возможность управления контекстами непосредственно в язык.
Рас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);
}
От возможности определения глобальных переменных и функций при наличии контекстов можно будет избавиться.
Как вам такая идея расширения языка? Может в каких-нибудь языках это уже реализовано?