Очень радует, что на Хабре появляются статьи о языке D. Но, на мой взгляд, переводы хелпа и статей для чуть больше, чем для новичков не дают ничего в плане популяризации языка. Думаю, искушённой публике лучше представлять, пусть более сложные, но какие-то интересные вещи — фишки. Большинство из того, что можно назвать фишками D, есть и в других языках, но многое в D реализовано более эффектно и эффективно, на мой вкус во всяком случае. В D есть много интересного, о чем стоит рассказать, и начну я в этой статье с функций, но не совсем обычных.
Писать о фишках языка, особенно достаточно нового, весьма чревато, в первую очередь, по причине «а тот ли термин использует автор?», «а полезно ли это вообще?» и «так ли уж правильно автор понял эту фичу?». Так что сильно не бейте, я сам в этом яз��ке не долго, буду рад на указание неточностей и оспаривание моих утверждений в комментах. Но думаю, в любом случае, это будет полезнее, чем описание очередного хеллоуворлда.
С анонимными функциями в D все хорошо. Реализация в стандарте полностью соответствует, тому что принято называть словами «анонимные функции» или «делегаты». Хорошо проглядывается сходство с удачными реализациями из других языков. Принцип «лишь бы не как у всех», разработчиков D не заботит и это хорошо.
Что здесь? Переменной 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.
Как уже писал, с делегатами все просто, они представлены в синтаксисе языка напрямую. Теперь немного другое. Прямой реализации в синтаксисе языка фичи, вынесенной в заголовок, нет, есть реализация в стандартной библиотеке, но не такая как представлена ниже. В библиотеке задействованы фишки, которые будут приведены в последнем разделе статьи. А здесь мы пойдем другим путем, путем змеи :). Как известно, в Python любая фун��ция — это объект, а любой объект, если в его классе определен метод __call__ может быть вызван как функция. Язык D предоставляет нам аналогичную возможность для объектов с помощью метода opCall. Если этот метод определен в классе, то экземпляр класса приобретает свойства функции. Есть методы с помощью которых, например, можно взять индекс (типо: obj[index]) и много чего еще. Таким же способом переопределяются операторы. В общем, это тема для отдельной большой статьи. Хочется сказать, что это было подсмотрено в Python, но знаю что эта концепция гораздо старше. Итак, частичное применение:
В примере есть две обычные функции, обобщенный случай возведения в степень и умножения. И есть класс, объекты которого благодаря спец методу opCall могут быть использованы как функции, поведение которых задается при создании объекта. Класс у нас получился со свойствами функции высшего порядка, принимает параметром функцию, определяющую поведение. А так же со свойствами частичного применения, один из параметров определяется в момент создания объекта-функции.
Таким образом, созданы два объекта-функции, одна возводит во вторую степень число, вторая умножает число на три. Практически все, что можно делать с обычными функциями, можно делать и с объектами такого типа, как далее в примере — передавать их в функцию высшего порядка.
Ну и напоследок, как водится, самое забавное. Представте себе, что стоит такая задача, написать функции, первая будет применять ко всем элементам массива чисел с плавающей точкой такую формулу: «sin(X) + cos(X)», а вторая для массива целых чисел такую: "( X ^^ 3) + X * 2". И небольшой сразу нюанс, что от релиза к релизу формулы будут меняться. Что на это ответит программист на D? «Да не вопрос, сколько угодно формул», и напишет одну обобщенную функцию.
Могу ошибаться, но прямого аналога нет, во всяком случае в популярных, компилируемых языках. Макросы С наиболее близко, но там это не будет так красиво выглядеть. Здесь задействованы сразу две фишки D: шаблоны и миксины, дающие в паре очень элегантную реализацию обобщенной функции. Шаблоны достаточно обычные, разве что выглядят не так пугающе как в С++. А миксин, хоть и напоминает макрос, но реализован по другому. За формирование результирующего кода ответственен не препроцессор, а сам компилятор, и поэтому строка миксина может быть вычислена во время компиляции.
Вообще D может делать очень многое во время компиляции. И миксины в паре с шаблонами представляют очень мощный инструмент, который широко задействован в стандартной библиотеке (phobos) языка. Большинство простых функций реализовано именно так. Это конечно имеет побочный эффект, для новичка в языке просмотр их исходного кода равносилен чтению «филькиной грамоты». Но позже, когда становится понятна суть этого метода, остается только одна эмоция — восхищение.
На этом я, пожалуй, откланяюсь. Буду рад если кто-то продолжит тему фишек в 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, там их еще немерено :)
