Фишки языка D

Очень радует, что на Хабре появляются статьи о языке D. Но, на мой взгляд, переводы хелпа и статей для чуть больше, чем для новичков не дают ничего в плане популяризации языка. Думаю, искушённой публике лучше представлять, пусть более сложные, но какие-то интересные вещи — фишки. Большинство из того, что можно назвать фишками D, есть и в других языках, но многое в D реализовано более эффектно и эффективно, на мой вкус во всяком случае. В D есть много интересного, о чем стоит рассказать, и начну я в этой статье с функций, но не совсем обычных.Писать о фишках языка, особенно достаточно нового, весьма чревато, в первую очередь, по причине «а тот ли термин использует автор?», «а полезно ли это вообще?» и «так ли уж правильно автор понял эту фичу?». Так что сильно не бейте, я сам в этом языке не долго, буду рад на указание неточностей и оспаривание моих утверждений в комментах. Но думаю, в любом случае, это будет полезнее, чем описание очередного хеллоуворлда.Анонимные функции они же делегаты, замыканияС анонимными функциями в D все хорошо. Реализация в стандарте полностью соответствует, тому что принято называть словами «анонимные функции» или «делегаты». Хорошо проглядывается сходство с удачными реализациями из других языков. Принцип «лишь бы не как у всех», разработчиков D не заботит и это хорошо. import std.stdio;

int do_something_with_x (int X, int delegate (int X) func) { return func (X); }

void main () { int Y = 10;

int delegate (int X) power2 = delegate (int X) { return X * X; }; auto powerY = delegate (int X) { return X ^^ Y; };

int mulY (int X) { return X * Y; }

writefln («power 2: %d», power2(4)); writefln («power 3: %d», (int X) { return X * X * X; }(4));

writefln («do_something_with_x; power2: %s», do_something_with_x (2, power2)); writefln («do_something_with_x; powerY: %s», do_something_with_x (2, powerY)); writefln («do_something_with_x; muxY: %s», do_something_with_x (2, &mulY)); writefln («do_something_with_x; anon: %s», do_something_with_x (2, (X){ return X*(-1); })); } Что здесь? Переменной power2 присвоен делегат, определенный по месту присваивания. Переменной powerY с автоматическим определением типа присвоен делегат, который использует в расчете локальную переменную Y, то есть делегат в D, это еще и замыкание. А mulY — это просто замыкание, не делегат, которая по причине этого факта передается в функцию do_something_with_x по ссылке. Кстати, функция do_something_with_x у нас функция высшего порядка. Столько модных слов в одном небольшом банальном примере, клево, правда же? Первый writefn банальный. Во втором writefn у нас определена анонимная функция и сразу же вызвана, это очень популярный ход, например в JavaScript. Ну и интересна последняя строка с writefn. Там в параметре функции do_something_with_x, определена анонимная функция, причем не указан тип параметров. Это нормально, так как тип анонимной функции или, если хотите делегата, четко прототипирован в определении функции do_something_with_x.Partial functions (Частичное применение) Как уже писал, с делегатами все просто, они представлены в синтаксисе языка напрямую. Теперь немного другое. Прямой реализации в синтаксисе языка фичи, вынесенной в заголовок, нет, есть реализация в стандартной библиотеке, но не такая как представлена ниже. В библиотеке задействованы фишки, которые будут приведены в последнем разделе статьи. А здесь мы пойдем другим путем, путем змеи :). Как известно, в Python любая функция — это объект, а любой объект, если в его классе определен метод __call__ может быть вызван как функция. Язык D предоставляет нам аналогичную возможность для объектов с помощью метода opCall. Если этот метод определен в классе, то экземпляр класса приобретает свойства функции. Есть методы с помощью которых, например, можно взять индекс (типо: obj[index]) и много чего еще. Таким же способом переопределяются операторы. В общем, это тема для отдельной большой статьи. Хочется сказать, что это было подсмотрено в Python, но знаю что эта концепция гораздо старше. Итак, частичное применение: import std.stdio;

int power (int X, int Y) { return X ^^ Y; }

int mul (int X, int Y) { return X * Y; }

class partial { private int Y; int function (int X, int Y) func;

this (int function (int X, int Y) func, int Y) { this.func = func; this.Y = Y; }

int opCall (int X) { return func (X, Y); } }

int do_partial_with_x (int X, partial func) { return func (X); }

void main () { auto power2 = new partial (&power, 2); auto mul3 = new partial (&mul, 3);

writefln («power2: %d», power2(2)); writefln («mul3: %d», mul3(2));

writefln («do_partial_with_x: %d», do_partial_with_x (3, power2)); writefln («do_partial_with_x: %d», do_partial_with_x (3, new partial (&mul, 10))); } В примере есть две обычные функции, обобщенный случай возведения в степень и умножения. И есть класс, объекты которого благодаря спец методу opCall могут быть использованы как функции, поведение которых задается при создании объекта. Класс у нас получился со свойствами функции высшего порядка, принимает параметром функцию, определяющую поведение. А так же со свойствами частичного применения, один из параметров определяется в момент создания объекта-функции.Таким образом, созданы два объекта-функции, одна возводит во вторую степень число, вторая умножает число на три. Практически все, что можно делать с обычными функциями, можно делать и с объектами такого типа, как далее в примере — передавать их в функцию высшего порядка.Обобщённые функции, шаблоны, миксины Ну и напоследок, как водится, самое забавное. Представте себе, что стоит такая задача, написать функции, первая будет применять ко всем элементам массива чисел с плавающей точкой такую формулу: «sin (X) + cos (X)», а вторая для массива целых чисел такую:»(X ^^ 3) + X * 2». И небольшой сразу нюанс, что от релиза к релизу формулы будут меняться. Что на это ответит программист на D? «Да не вопрос, сколько угодно формул», и напишет одну обобщенную функцию. import std.math; import std.stdio;

T[] map (T, string Op)(T[] in_array) { T[] out_array; foreach (X; in_array) { X = mixin (Op); out_array ~= X; } return out_array; }

void main () {

writeln (»#1 », map!(int, «X * 3»)([0, 1, 2, 3, 4, 5])); writeln (»#2 », map!(int,»(X ^^ 3) + X * 2»)([0, 1, 2, 3, 4, 5]));

writeln (»#3 », map!(double, «X ^^ 2»)([0.0, 0.5, 1.0, 1.5, 2.0, 2.5])); writeln (»#4 », map!(double, «sin (X) + cos (X)»)([0.0, 0.5, 1.0, 1.5, 2.0, 2.5])); } Могу ошибаться, но прямого аналога нет, во всяком случае в популярных, компилируемых языках. Макросы С наиболее близко, но там это не будет так красиво выглядеть. Здесь задействованы сразу две фишки D: шаблоны и миксины, дающие в паре очень элегантную реализацию обобщенной функции. Шаблоны достаточно обычные, разве что выглядят не так пугающе как в С++. А миксин, хоть и напоминает макрос, но реализован по другому. За формирование результирующего кода ответственен не препроцессор, а сам компилятор, и поэтому строка миксина может быть вычислена во время компиляции.Вообще D может делать очень многое во время компиляции. И миксины в паре с шаблонами представляют очень мощный инструмент, который широко задействован в стандартной библиотеке (phobos) языка. Большинство простых функций реализовано именно так. Это конечно имеет побочный эффект, для новичка в языке просмотр их исходного кода равносилен чтению «филькиной грамоты». Но позже, когда становится понятна суть этого метода, остается только одна эмоция — восхищение.На этом я, пожалуй, откланяюсь. Буду рад если кто-то продолжит тему фишек в D, там их еще немерено :)

© Habrahabr.ru