Язык программирования D: функциональные типы и делегаты
Содержание:
1. Массивы и строки;
2. Семантика и шаблоны;
3.Функциональные типы и делегаты (Вы читаете данный раздел);
4. Модули и их тестирование;
5. Контрактное программирование.
В С++ передать функцию как параметр или сохранить ее в массиве можно только с помощью указателя, а синтаксис указателей на функции в С++ просто устрашающий. Чтобы хоть как-то облегчить жизнь в С++ появились boost::function, boost::bind и им подобные решения. В D проблема элегантно решается с помощью переменных функционального типа:
Кроме того, в D легко работать с парадигмой функционального программирования. В этом случае функция является «ленивой» - она не вычисляет нечто сама, а возвращает другую функцию, вычисляющую то, что нужно. В С++ «ленивые» вычисления осуществляются с помощью «магического» применения шаблонов и выглядят очень сложно (показательный пример - библиотека boost::phoenix2). В D все нужные средства встроены в язык. В качестве примера создадим массив из 10 функций. Первая добавляет к своему аргументу 1, вторая - 2, и т.д.
Функция do_plus - шаблонная, имеет два списка аргументов. Первый - перечисление шаблонных параметров, а второй -собственно аргументы. Сразу видно выгодное отличие синтаксиса от С++:
Функция do_plus возвращает делегата - анонимную функцию особого типа. В отличие от лямбда-функции делегат «помнит» контекст своего создания и может использовать любые символы той функции, которая его «породила» (такие функции называют замыканиями). Мы, например, прибавляем аргумент функции-родителя p к аргументу самого делегата val.
В функции main мы создаем массив из 10 делегатов и наполняем его в цикле так, что i-й делегат прибавляет к своему аргументу i. Затем вызываем в цикле всех делегатов, передавая им число 100, и ожидаемо получаем на выходе 100, 101, 102... 109.
Шаблонной функции можно передать аргумент любого типа, но в примере мы хотим работать только с числами. Странная конструкция if (is(typeof(p == 1.0) == bool)) называется ограничением сигнатуры шаблонной функции, вызывается только на стадии компиляции и призвана отсеивать аргументы неподходящего типа.
Если выражение p == 1.0 осмысленно (например, для типов int или double), то typeof вернет bool, а оператор сравнения типов is вернет true и компилятор продолжит работу. Однако для аргумента типа string сравнение p == 1.0 бессмысленно, а его тип - «никакой», т. е. не bool. Оператор is вернет false и компилятор выдаст ошибку.
Ограничение сигнатуры - очень мощный механизм, делающий шаблонные функции «дуракоустойчивыми». Насколько мне известно, он не имеет аналогов в других языках.
1. Массивы и строки;
2. Семантика и шаблоны;
3.
4. Модули и их тестирование;
5. Контрактное программирование.
Функциональные типы и делегаты
В С++ передать функцию как параметр или сохранить ее в массиве можно только с помощью указателя, а синтаксис указателей на функции в С++ просто устрашающий. Чтобы хоть как-то облегчить жизнь в С++ появились boost::function, boost::bind и им подобные решения. В D проблема элегантно решается с помощью переменных функционального типа:
void do_sqr(int x){ writeln(x*x); } // Сама функция
// Переменная функционального типа
void function(int)
fun = &do_sqr; fun(5); // Вызов исходной функции
// Переменная функционального типа
void function(int)
fun = &do_sqr; fun(5); // Вызов исходной функции
Кроме того, в D легко работать с парадигмой функционального программирования. В этом случае функция является «ленивой» - она не вычисляет нечто сама, а возвращает другую функцию, вычисляющую то, что нужно. В С++ «ленивые» вычисления осуществляются с помощью «магического» применения шаблонов и выглядят очень сложно (показательный пример - библиотека boost::phoenix2). В D все нужные средства встроены в язык. В качестве примера создадим массив из 10 функций. Первая добавляет к своему аргументу 1, вторая - 2, и т.д.
import std.stdio;
T delegate(T) do_plus(T)(T p)
if (is(typeof(p == 1.0) == bool))
{
return delegate(T val) { return val+p; };
}
void main() {
auto functions = new int delegate(int)[10]; foreach(i;0..10) functions[i] = do_plus(i); foreach(func;functions) writeln(func(100));
}
T delegate(T) do_plus(T)(T p)
if (is(typeof(p == 1.0) == bool))
{
return delegate(T val) { return val+p; };
}
void main() {
auto functions = new int delegate(int)[10]; foreach(i;0..10) functions[i] = do_plus(i); foreach(func;functions) writeln(func(100));
}
Функция do_plus - шаблонная, имеет два списка аргументов. Первый - перечисление шаблонных параметров, а второй -собственно аргументы. Сразу видно выгодное отличие синтаксиса от С++:
template
T func(T x); // С++ T func(T)(T x); // D
T func(T x); // С++ T func(T)(T x); // D
Функция do_plus возвращает делегата - анонимную функцию особого типа. В отличие от лямбда-функции делегат «помнит» контекст своего создания и может использовать любые символы той функции, которая его «породила» (такие функции называют замыканиями). Мы, например, прибавляем аргумент функции-родителя p к аргументу самого делегата val.
В функции main мы создаем массив из 10 делегатов и наполняем его в цикле так, что i-й делегат прибавляет к своему аргументу i. Затем вызываем в цикле всех делегатов, передавая им число 100, и ожидаемо получаем на выходе 100, 101, 102... 109.
Ограничения сигнатур
Шаблонной функции можно передать аргумент любого типа, но в примере мы хотим работать только с числами. Странная конструкция if (is(typeof(p == 1.0) == bool)) называется ограничением сигнатуры шаблонной функции, вызывается только на стадии компиляции и призвана отсеивать аргументы неподходящего типа.
Если выражение p == 1.0 осмысленно (например, для типов int или double), то typeof вернет bool, а оператор сравнения типов is вернет true и компилятор продолжит работу. Однако для аргумента типа string сравнение p == 1.0 бессмысленно, а его тип - «никакой», т. е. не bool. Оператор is вернет false и компилятор выдаст ошибку.
Ограничение сигнатуры - очень мощный механизм, делающий шаблонные функции «дуракоустойчивыми». Насколько мне известно, он не имеет аналогов в других языках.