Известно, что языки программирования бывают статические и динамические. В статических языках типы всех значений известны в момент компиляции. В результате компилятор может проверить, правильно ли используется значение, применима ли к нему та или иная операция. Узнавать об ошибке в момент компиляции приятнее, чем во время исполнения — меньше ошибок выйдет при тестировании и дойдет до пользователя. За это ошибок и ценят статические языки.

Но почему дело ограничивается только типами данных? Попробуем немного по-фантазировать, что еще мог бы делать компилятор.

Допустим, у нас есть константа для количества секунд в сутках:
const int secondsPerDay = 24*60*60;

Мы не стали перемножать числа на калькуляторе, чтобы при чтении кода было поянтно как число посчитано (а может просто не было под рукой калькулятора). Но программа не обязана при каждой инициализации ��еремножать эти числа заново! Пусть компилятор перемножит их сам и поставит готовое значение. Я подозреваю, что хороший компилятор так и сделает.

Переходим в область научной фантастики (как в предметной области, так и в С++).

Мы решили выпускать локализованную для жителей Марса версию нашей программы:
enum Planet{
    Earth,
    Mars
}

const Planet locale = Mars;

int secondsPerDay() {
    if(locale == Earth) {
        return 24*60*60;
    }
    if(locale == Mars) {
        return 24*60*60+37*60+23;
    }
    assert(false, "unknown locale");
}    


Функция secondsPerDay() полностью константная. Для откомпилированного кода она всегда возвращает одно и то же значение. Поэтому наш гипотетический компилятор в праве вычислить значение этой функции, и подставить его по месту вызова. А если мы потом добавим Венеру, забыв исправить secondsPerDay(), то при компиляции с locale = Venus сработает assert (при компиляции, а не при выполнении, как это бывает в жизни).

Вспомним псевдо-функцию sizeof(), известную еще со времен C. Эта конструкция выглядит как обычная функция, но на самом деле компилятор вычисляет количество байт, которое занимает в памяти аргумент функции. Во время выполнения никакого вызова функции уже не происходит. К области вычислений, производимых компилятором, можно также отнести шаблоны C++. Но более гибкие возможности почему-то не поддерживаются.

Рассмотрим пример по-интереснее. Напишем функцию, которая будет генерировать SQL — запрос на основании имени типа структуры, и ее полей:

// Т - тип структуры, имя типа соответствуе имени таблицы
// имена полей структуры - колонкам таблицы
T fetchById<T>(Connection conn, int id){
    
    // статические проверки!!!
    assert(is_struct(T), name_of(T)+" is not struct");
    assert(has_field(T, id), name_of(T)+ " doesn't contains field id");
    
    string query = "SELECT ";
    // статический цикл! текст запроса формируется компилятором полностью
    foreach (field f in T) {
        if(field.index != 0) {
            query += ", ";
        }
        query += field.name;
    }
    query += "FROM " + name_of(T) + " WHERE ID=?ID";

    // пропущен динамический код выполняющий наш запрос
......................................................................................................................
    // в итоге мы получаем некий объект Record r, из которого можно получить значения полей по имени

    // кажется с C++ мы плавно перешли на Java? не обращайте внимание
    T result = new T;

    // компилятор превращает этот цикл в последовательность вызовов для каждого поля структуры
    foreach (field f in T) {
        result[f] = r.getFieldValue<f.type>(f.name);
    }  
    
    return result;

}


Теперь наша функция уже не константная, но отдельные ее фрагменты — константны, и гипотетический компилятор выполняет целые циклы, заменяя их константыми значениями или последовательностями операторов.

И не говорите, что тут изобретено RTTI! RTTI это run time type information, она работает во время выполнения программы. Здесь информация о типе используется во время компиляции для выполнения компилятором константных инструкций.

Итак, получив структуру как результат функции мы наслаждаемся статической проверкой при обращении к именам структуры, в место того чтобы обращаться к полям записи по строковому представлению. Правда, если наша структура впрограмме не совпадет с действительными полями в базе данных, мы опять налетим на ошибку выполнения. Но по крайней мере код, от которого это зависит локализован в определении структуры. Для подстраховки мы можем написать такую же универсальную функцию, которая проверит соответствие базы данных заявленным типам, а если надо — выполнит реструктуризацию базы.

Любопытно, что приведенная задача решена как минимум в двух статических языках (в динамических она решается без проблем).

Втроенный в C# и VB.NET язык LINQ позволяет статически работать с базами данных. Многие возможности были реализованы вполне легально: лямбда выражения, анонимные типы, операторные формы методов. Но для сопряжения статически объявленных классов с таблицами и полями базы данных Microsoft применил магию восьмого уровня под названием «integration of SQL schema information into CLR metadata» [1].

Другой пример — библиотека HaskellDB [2,3]. Сам Хаскель, с его монадами, можно расценить как магию первого уровня. Но разработчикам понадобилась магия третьего уровня в виде нестандартного расширения языка под названием «расширяемые структуры Trex». И не смотря на это, для увязки с базой данных каждому полю струкуры приходится давать избыточное объявление. Пример объявления таблицы сдвумя полями:
students :: Table(name :: String, mark :: Char)
name :: r\name =>
Attr (name :: String | r) String
mark :: r\mark =>
Attr (mark :: Char | r) Char


Чтобы яснее представить пользу от наших новшеств, предлагаю еще примеры.
У нас есть две структуры:
struct Order{
    Date date;
    Client client;
    Product product;
    int qty;
    Numeric cost;
};

struct Sales{
    Product product;
    int qty;
    Numeric cost;
};


Где-то мы решили скорировать данные из Order в Sales
void foo(Order o, Sales s){
    foreach(field f in Sales){
        s[f.name] = o[f.name];
    }
}


Теперь мы можем добавлять в наши поля новые структуры не меняя функцию foo()! А если вычисление foo() станет невозможным из-за несовпадения полей мы получим ошибку компиляции.

Пример из области объектно-ориентированного дизайна:

class Shape{
..............................
};

class Square: public Shape{
public:
    Square();
};

class Circle: public Shape{
public:
    Square();
};

....................................
// представим, что мы считываем данные из файла
// и должны конструировать объекты, в зависимости
// от указанных там данных
Shape* shapeFactory(string shapeName) {
    foreach(type T in descendants(Shape)){
        if(name_of(T) == shapeName){
            return new T;
    }
    return null;
}


В процессе компиляции shapeFactory() превратится в обычную функцию:

Shape* shapeFactory(string shapeName) {
    if("Square" == shapeName){
        return new Square; 
    }
    if("Circle" == shapeName){
        return new Circle;
    }
    return null;
}


Ссылки:
1. LINQ: .NET Language-Integrated Query
2. Официальная страница проекта HaskellDB
3. Daan Leijen, Eric Meijer. Domain Specific Embedded Compilers. Статья, рассказывающая как устроен HaskellDB. Там же обсуждается проблема сопряжения SQL и языка программирования