Язык программирования D — продолжение

    Доброго всем времени суток!
    Итак, я решил продолжить рассказ о замечательном языке программирования D.
    Моя прошлая статья была о мультипарадигменности языка, о том, что он естественным и гармоничным образом поддерживает большинство современных популярных стилей программирования.
    В этот раз я задумал осветить другую сторону языка — менее общую и фундаментальную, но не менее полезную. А именно возможности метапрограммирования и compile-time computations.


    Начну я, пожалуй со всем привычных вещей — обобщенного программирования (generics, templates). То есть, со знакомых всем нам по C++ шаблонов.

    Для начала, что это такое на простом уровне: обобщенное программирование (шаблонные функции и типы данных) — это способ предоставить возможность повторного использования кода. Когда программист пишет код для некоторого обобщенного типа, а потом подставляет конкретные.
    В языке D выбран гетерогенный подход. Это значит, что шаблоны в недрах реализации языка — не более, чем типобезопасные макросы, и каждый конкретный тип, подставленный в шаблон просто-напросто генерирует отдельную реализацию. Попробуем описать простую, и, признаю, мало осмысленную шаблонную функцию:

    T[] MapInc(T)(T[] arr)
    {
    	auto res = new T[arr.length];
    	foreach(i, v; arr)
    		res[i] = v + 1;
    	return res;
    }


    Первым делом замечу, что возвращаемый результат — тоже параметризируется. Очень удобное свойство.
    Посмотрим, как она работает:

    void main()
    {
      auto ar = [1,2,3,4];
      auto ard = [1.0,2.0,3.0,4.0];
      assert(MapInc(ar) == [2,3,4,5], "wrong!");
      assert(MapInc(ard) == [2.0,3.0,4.0,5.0], "wrong!");
    }


    Все тесты прошли, как и предполагалось! Несомненно, успех. Хотя постойте… попробуем такой код:

    auto ar = ["1","2","3","4"];
    MapInc(ar);


    Не скомпилировалось? Не удивительно. Ошибка компиляции на строке
    res[i] = v + 1;

    и, как следствие, на строке
    MapInc(ar);

    А теперь представим, что функция эта написана не нами, а зарыта глубоко в библиотеке. Безрадостно, верно?
    Однако, D предоставляет очень удобный способ разрешить возникшее недоразумение. Так как одна строчка кода заменит тысячу слов, просто взглянем:

    T[] MapInc(T)(T[] arr)
      if(is(typeof(arr[0] + 1) == typeof(arr[0])))		// вот сюда смотреть.
    {
    	auto res = new T[arr.length];
    	foreach(i, v; arr)
    		res[i] = v + 1;
    	return res;
    }


    Таким образом, мы просто и элегантно указали компилятору, для каких входных данных предназначена функция, и если он найдет код, который выше давал ошибки сразу в двух строчках, да еще одну прямо в функции, то он проверит условие и сгенерирует ошибку прямо на месте. И, что самое главное, это совершенно бесплатно!
    Конечно, под бесплатно, я имею ввиду тот факт, что все подобного рода проверки проходят на этапе компиляции, не отнимая ни одного драгоценного такта в рантайме. Это, кстати, значит, что мы можем писать только те проверки, которые возможно вычислить на этапе компиляции.
    А теперь вернемся к коду:
    if(is(typeof(arr[0] + 1) == typeof(arr[0])))

    Вам, наверное, это кажется полнейшим шаманством. Как же вычислить на этапе компиляции arr[0] да еще и + 1? Правильный ответ — а никак. Компилятор и не вычисляет это значение. В данном случае typeof не вычисляет собственный аргумент, оно просто выводит тип значения, которое передается ему, как аргумент.
    Таким образом
    typeof(arr[0] + 1) == typeof(arr[0])

    значит лишь то, что:
    a) Мы можем к значениям типа typeof(arr[0]) прибавлять значения типа typeof(1) и
    b) После прибавления мы снова получим тип typeof(arr[0]).
    Здорово, не правда ли?

    Итак, мы написали функцию, которая умеет прибавлять ко всем значениям массива единицу, при том она аккуратно докладывает, если прибавлять единицу нельзя в принципе. Вроде неплохо? Однако, дальше — лучше.
    Чтобы дойти до лучше, придется еще немного попотеть и модифицировать пример:

    T[] MapInc(T)(T[] arr, T b)
      if(is(typeof(arr[0] + b) == typeof(arr[0])))
    {
    	auto res = new T[arr.length];
    	foreach(i, v; arr)
    		res[i] = v + b;
    	return res;
    }


    Теперь мы прибавляем не единицу, а произвольное число. Проверим нашу функцию:

    void main()
    {
      auto ar = [1,2,3,4];
      assert(MapInc(ar,3) == [4,5,6,7], "wrong!");
    }


    Успех! Но постойте, мы обошлись с ней слишком мягко, попробуем несколько усложнить задачу:

    void main()
    {
      auto ar = [1.0,2.0,3.0,4.0];
      assert(MapInc(ar,1) == [2.0,3.0,4.0,5.0], "wrong!");	// ошибка, несоответствие типов.
    }


    Упс! Компилятор не смог понять наш восхитительный замысел. Что это: очередная сварливая строгая система типов желает нам зла? Но давайте посмотрим повнимательнее: мы же сами виноваты! В определении четко сказано, что массив и число одного типа. Хорошо. Попробуем еще модифицировать пример, чтобы принимать значения разных типов.

    T[] MapInc(T,P)(T[] arr, P b)
      if(is(typeof(arr[0] + b) == T))
    {
    	auto res = new T[arr.length];
    	foreach(i, v; arr)
    		res[i] = v + b;
    	return res;
    }


    Вот теперь уже окончательный успех:

    void main()
    {
      auto ar = [1.0,2.0,3.0,4.0];
      assert(MapInc(ar,1) == [2.0,3.0,4.0,5.0], "wrong!");	// ура, выполняется!
    }


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

    Теперь настало время пояснения любителям строгости. Такой вариант записи вызова функции:
    MapInc(ar,1);

    Всего лишь результат автоматического вывода типов. А вот как дела обстоят на самом деле:

    В D каждая функция имеет два набора аргументов и в общем виде определяется как
    T f(c1,c2/*, others*/)(r1,r2/*, others*/);

    Первый набор — набор аргументов времени компиляции, второй — времени выполнения.
    Название говорит само за себя: аргументы из первого набора вычисляются во время компиляции, в то время, как из второго — во время выполнения. И то не всегда.
    Таким образом все аргументы времени компиляции опять-таки достаются нам «на халяву», но снова мы не можем использовать тут невычислимые в течении компиляции значения.

    Для вызова полиморфных функций синтаксис таков:
    auto v = f!(c1,c2/*, others*/)(r1,r2/*, others*/);

    При этом, если аргумент времени компиляции всего один, то можно опустить скобки:
    auto v = f!c1(r1,r2/*, others*/);


    Аргументами времени компиляции могут быть не только типы, а вообще любые выражения, вычислимые на этапе компиляции. Например, «42».
    Но 42 сгодится и в шаблонах C++, тут же все гораздо интереснее: в качестве таких параметров можно использовать даже функции! Рассмотрим пример:

    P[] Map(alias f,T,P)(T[] arr)
      if(is(typeof(f(arr[0])) == P))
    {
    	auto res = new P[arr.length];
    	foreach(i, v; arr)
    		res[i] = f(v);
    	return res;
    }
    
    void main()
    {
      auto ard = [1.0,2.0,3.0,4.0];
      auto ar = [1,2,3,4];
      assert(Map!((double x) {return x+1;},double,double)(ard) == [2.0,3.0,4.0,5.0], "wrong!");
      assert(Map!((int x) {return x+1.0;},int,double)(ar) == [2.0,3.0,4.0,5.0], "wrong!");
      assert(Map!((int x) {return x+1;},int,int)(ar) == [2,3,4,5], "wrong!");
      assert(Map!((double x) {return x+1.0;},int,double)(ar) == [2.0,3.0,4.0,5.0], "wrong!");
    }


    В данном случае мне пришлось помочь компилятору и вручную определить типы для шаблонных параметров. Не уверен, но наверное можно как-то определить эту функцию так, чтобы этого не понадобилось.
    Предлагаю устроить конкурс на самую красивую реализацию map в комментах :)
    Но с другой стороны — посмотрите как здорово получилось!

    Сравним этот подход с подходом C++11 STL на примере широко известного алгоритма sort.

    auto arr = {1,2,3,4};
    sort(arr.begin(), arr.end(), [&](int a, int b) {return a > b && a < 42;});


    Что тут происходит? Правильно, сортировка массива, при этом чтобы определить, нужно ли менять элементы местами их нужно сравнивать и для этого вызывается наша лямбда-функция (спасибо что хоть теперь лямбда, раньше приходилось функцию писать). Вызывается каждый раз. А в данном (да и, наверное, во многих) случае затраты на вызов функции сравнимы с временем вычисления самой функции, если даже не превосходят его. Непростительная растрата производительности.
    В то время, как в D, мы видели, функция — аргумент времени компиляции, а соответственно и вычисляется во время компиляции, а соответственно элементарно инлайнится. Так что, можно сказать, кое в чем D даже превосходит C++ по эффективности, как говорится, by design.

    Статью уже разнесло, а ведь я только начал!

    Итак, продолжим с вычислениями в процессе компиляции.

    Все мы помним, а нерезко и грешим выражениями типа
    #ifdef P
    ...
    #else
    ...
    #endif


    В D нет препроцессора (кто-то скажет: «и слава богу», кто-то поморщится, но холивар устраивать не будем, а продолжим читать).
    Однако, в языке есть конструкции, его заменяющие. Например, вышеприведенную конструкцию, заменит выражение static if. Вообще, мы обошлись с ним очень (поверьте, ну ооочень) несправедливо: static if может несравненно больше.
    Тут нужно лирическое отступление. В D есть ключевое слово alias. Оно работает много как, но сейчас оно нам потребуется в качестве typedef. Вот пример:
    alias int MyOwnPersonalInt;

    Итак, static if. Попробую показать на деле:
    
    enum Arch {x86, x64};
    static Arch arch = x86;
    
    static if(arch == x86)
      alias int integer;
    else
      alias long integer;


    Мы определили целочисленный тип, размер которого зависит от архитектуры машины, на которой компилируется код.
    Теперь можем его использовать как любой другой тип:
    integer Inc(integer n) {return n+2;}


    static if можно писать буквально где угодно: В глобальном коде, в функциях и даже в определениях классов!
    Тут нужно небольшое добавление, выражения «static else» нету! Используйте обычный else, коллизий возникнуть не может, вложенность учитывается.

    И в заключение еще одна интересная и полезная особенность. Одним из принципов идеологии языка является: вычислять во время компиляции все, что может быть вычислено.
    Рассмотрим такой код:

    static int a = f();
    // или даже
    enum int b = f();


    Язык предполагает, что компилятор проверяет возможность вычислить f() во время компиляции используя интерпритатор, и если это невозможно, генерируется ошибка, иначе значение просто подставляется в код. Для D общепринятая практика — писать функции, которые должны вычислиться во время компиляции.
    Еще раз: в отличии от C/C++ static переменные инициализируются не при первом вызове инициализирующего кода, а во время компиляции программы и соответственно должны быть инициализированными выражениями, вычислимыми во время компиляции.

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

    Надеюсь, свое дело я сделал хорошо и эта статья сможет заинтересовать людей языком программирования D.
    Тем, кто смог дочитать до сюда — надеюсь, вам понравилось и спасибо за внимание!

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

    Поделиться публикацией

    Похожие публикации

    Комментарии 75

      +2
      assert(Map!((double x) {return x+1.0;},int,double)(ar) == [2.0,3.0,4.0,5.0], "wrong!");
      


      Выглядит страшновато. На пару строк можно перенести?
        +3
        Это пожелание к статье или вопрос про язык?
        Если второе, то да, насколько я знаю, во всех языках в си подобным синтаксисом можно дробить выражения на несколько строк, а если первое, то не уверен, что будет сильно лучше.
          0
          И то и то.
            0
            Я бы предложил такой вариант:
            auto incrementer = (double x) {return x+1.0;}
            assert(Map!(incrementer,int,double)(ar) == [2.0,3.0,4.0,5.0], "wrong!");
            

            0
            В этой строке много текста, но немного смысла. Зачем дробить тривиальное выражение на строки?
              +2
              … эм, судя по минусам, я должен пояснить. Данное выражение содержит многабукв, но с позиции чистой вычислительной логики являет собой простую идиому, которая на языке Haskell, например, будет выглядеть примерно так:

              assert (map (+ 1.0) ar == [2.0,3.0,4.0,5.0]) "good!"

              Теперь видно, что ничего страшного тут нет. Если вы активно пользуетесь функциями высшего порядка в своём любимом языке программирования (будь то Haskell, Python или D), то беглое чтение таких идиом — даже на других, более многословных языках — не составляет для вас труда, и разбивка элементарных выражений на строки выглядит скорее вредной, чем полезной.

              Уметь бегло читать такие выражения очень важно, поскольку очень важно уметь игнорировать синтаксис и сосредотачиваться на семантике, чтобы сразу же видеть в данном примере не map и \-функцию, а некорректное сравнение чисел с плавающей точкой на равенство.
                0
                Я думаю, я понимаю, почему людям не понравилось. Вы утверждаете мало смысла, а на самом деле:
                -мы создаем лямбда-функцию
                -тут же ее вызываем
                -проверяем результирующее значение
                -бросаем эксцэпшн с сообщением если не совпало.

                Смысла как раз совсем не мало.
                И не не надо сравнивать с хаскелом — он вообще чрезвычайно лаконичный язык, с ним что хочешь многословно выглядит :)
                  +1
                  Рискуя снова нарваться, скажу, что, согласно моему мнению, если язык абстрагирует некоторую деятельность, то нужно руководствоваться этими абстракциями (до тех пор, пока абстракция не сломается и не возникнет необходимость залезать вглубь). Приведу пример — реальную строчку из исходного кода ядра Linux:

                  hostdata->busy[cmd->device->id] |= (1 << cmd->device->lun);

                  — мы разыменовываем указатель на массив
                  — разыменовываем указатель на устройство и идентификатор устройства
                  — используем идентификатор как индекс массива
                  — разыменовываем ещё два указателя
                  — производим побитовый сдвиг
                  — хитро записываем результат в переменную.

                  Кошмар! Полэкрана ассемблерных инструкций в результате компиляции одной строчки кода!

                  И, в принципе, эту строчку можно отрефакторить. Например, вынести cmd->device и hostdata->busy в отдельные переменные. Но зачем? Только первокурснику будет легче читать результирующий код. Опытный системный программист читает эту строчку на одном дыхании и сразу понимает зафиксированные в ней идиомы, а всякие лишние определения переменных, наоборот, замедляют чтение, поскольку приходится расширять удерживаемый в кратковременной памяти контекст.

                  Некогда об этом же писал Стив Йегг, но о чём он только не писал :-)
                    0
                    Не холивара ради, но такого рода кода из сабжевой строки получится немало. Ну и вообще, число смысла на единицу кода каждым воспринимается по-разному, я на ассемблере программировал, после этого для меня каждая строка кода на C/C++/D/etc наполнена смыслом по самое нехочу.
                    Строки, написанные мной кажутся мне в самый раз, и я, как автор статьи, имею право решающего голоса, и хоть я и соглашусь с вашими доводами, но те строки удовлетворяют моим эстетическим запросам, потому я так и оставил, но в самом деле. не могу же я всем угодить!
                    В дополнении замечу, что по-моему вы как-то своими «только первокурснику» и «опытный системный программист» принимаете несколько оскорбительный тон. Получается, все, у кого несовместимые с вашими взгляды — неопытные первокурсники. И именно поэтому вы «рискуете снова нарваться», а не из за отличного от других мнения.
                      –1
                      Конечно, «в дополнение», больше суток не спал, внимательность падает.
                        0
                        Строки, написанные вами, и мне кажутся в самый раз :-)

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

                        Вообще, на мой взгляд, есть ещё один важный вопрос. Если кому-то в команде, работающей над проектом, сложно читать код, то код нужно рефакторить вне зависимости от регалий всех остальных программистов. А при поддержке всей команды код можно осторожно усложнять. Важны не формальности, а люди и дифференцированный подход. В т. ч. по этой причине я не люблю замечания по форматированию кода в комментариях к статьям о программировании — объективной истины тут не достигнуть, но легко скатиться во вкусовщину и навязывание своего мнения.
                        –1
                        И, в добавок, я не понял, в чем вы несогласны со списком действий, приведенных мной выше. Я же полностью учел уровень абстракции языка, я же не заявлял пунктов наврод «создание функционального обьекта-указателя» или «спецификация шаблонной функции конкретными типами».
                      –1
                      Упс, забыл еще: вызываем функцию с аргументом в виде этой лямбда-функции и еще одним.
                      +1
                      С использование стандартной библиотеки и «настоящего» map будет выглядеть несколько лаконичнее:
                      assert( equal( map!("a + 1.0")(ar) , [ 2.0, 3.0, 4.0, 5.0 ] ), "wrong!");

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

                      P.S. equal, а не == т.к. идёт сравнение двух range, строго говоря, являющихся разными типами.
                        0
                        Со вчерашнего дня, в DMD Git был добавлен синтаксис построения лямбда-функций. Теперь можно писать так:

                        assert(Map!(x=>x+1.0, int, double)(ar) == [2.0,3.0,4.0,5.0], "wrong!");
                          0
                          Ура! Действительно хорошая новость.
                        +7
                        Если бы название языка начиналось с двоеточия, то он бы пользовался гораздо большей популярностью…
                          +2
                          В процессе написания статьи у меня где-то возникло «D:», но я подумал, поставил там точку и начал новое предложение :)
                          +1
                          > для этого вызывается наша лямбда-функция [...]. Вызывается каждый раз.

                          Не правда, всё отлично инлайнится.
                            –1
                            Это возможно заилайнить. Но если там будет не лямбда в явном виде, а какой-нибудь указатель на функцию, то уже нельзя. Ну или сложно — вдруг кто-то этот указатель переприсвоил другой функции в рантайме? А в D полностью статическое вычисление обеспечено языком.
                              0
                              Я и не спорю, но в данном случае, приведённом в статье, всё инлайнится. А случае из вашего комментария — нет. А код в стандартной библиотеке один и тот же. Так что, тут у C++ преимущество: инлайнится когда возможно, и дублирование кода для покрытия обоих случаев не требуется.
                              +1
                              Еще добавлю: есть разница между «статическое вычисление обеспечено стандартом языка» и «понадеемся, что компилятор сообразит, что выражение можно вычислить статически».
                                0
                                А в стандарте написано что инлайнинг обязан произойти? И кстати, у D, насколько я знаю, нет стандарта…
                                  0
                                  А где я говорил про инлайнинг? Я говорил про гарантию статическово вычисления выражений. Инлайнинг просто после этого легко осуществить.
                                    –1
                                    Тогда у D нет никаких преимуществ перед C++ нет даже в этом случае, если компилятор может заинлайнить, а может и нет (потому что не умеет, потому что эвристика решила что не стоит или по любой другой причине).
                                      –6
                                      У D вообще нету преимуществ. Ни перед C, ни перед C++. Искренне ваш, К.О.
                              +1
                              В свое время удивила реализация однобайтовых строк и отсутствие нативной поддержки юникода. Это до сих пор так?
                                0
                                UTF8 рулит. UTF16 всё равно содержит суррогатные пары, так что не даёт простоты. А UTF32 слишком неэкономный. Это к слову об однобайтовых строках. А вот отсутствие поддержки юникода — это плохо.
                                  +3
                                  Давненько вы смотрели, однако (:
                                  Когда я в ВУЗе 8 лет назад рассказывал про D, в языке уже была поддержка Unicode. Сейчас и подавно есть: модули std.utf и std.uni специально для работы с Unicode, а так, в общем и целом, все стандартные строковые функции работают с UTF-8, UTF-16 и UTF-32, и исходники тоже могут быть в этих кодировках, причём и в big-, и в little-endian.
                                    +1
                                    Да, вы определенно обладаете устаревшими данными. Со строками в D как раз все замечательно, мне даже больше чем в C++ нравится, учитывая, что строки не являются чем-то особенным, а просто immutable(char)[]. То есть они просто массивы неизменяемых символов, никаких новых сущностей не вводится. Это хорошо не только эстетически, а, например, тем, что моя реализация Map на них тоже будет работать.
                                    +2
                                    Как там насчет многопоточности? Есть какой-то аналог green threads (легких нитей)?
                                      +1
                                      Читайте официальный сайт, особенно раздел про стандартную библиотеку. Лёгких нитей нет, но обычные треды есть, через модуль core.thread. Я в во всей этой потоковщине не силён, но знаю точно, что по умолчанию все глобальные переменные помещаются в TLS (Thread Local Storage), а чтобы расшарить их между тредами, нужно писать shared (внезапно :)
                                        0
                                        Насчет потоков я еще глубоко не разбирался, но в язык встроена некоторая поддержка. Как выше написали: shared память, далее, syncronized вычисления, да и чистый функциональный стиль — большое подспорье.
                                          0
                                          Да, еще есть immutable память — такую вообще очень удобно между потоками перекидывать, так как она гарантированно неизменяема.
                                            +2
                                            Там всё весьма хорошо с многопоточностью. Green threads нет и, насколько мне известно, не планируется. «D-way» многопоточности на данный момент — message passing. Конечно же поддерживаются и треды в стиле pthread, но в чистом виде к использованию не рекомендуются, т.к. слишком error-prone.

                                            www.informit.com/articles/article.aspx?p=1609144 — опубликованная в открытый доступ глава из книги Александерсу «The D Programming Language», посвящённая concurrency вообще.

                                            www.d-programming-language.org/phobos/std_concurrency.html — стандартная message-passing библиотека

                                            www.d-programming-language.org/phobos/std_parallelism.html — symmetric multiprocessing (SMP)

                                            Есть нативная поддержка в самом языке для синхронизации доступа к данным, основанная на системе типов. Тут парой линков не отделаться, гуглить сайт D по synchronized, shared, immutable

                                            Если возникнут какие-то конкретные практические вопросы — с удовольствием отвечу.
                                              +1
                                              Есть Fibers
                                              0
                                              Какая у D изюминка, которая цепляет? Я давно еще смотрел этот язык, но так и не нашел той изюминки. Например, у Ruby это RoR, у Go есть routines, а Java, C/C++, Python просто занимают почетное место благодаря зрелости.

                                              Думаю проблема низкой популярности D в отсутствии изюминки, а если она и есть то на неё не заостряют внимание и в итоге ни рыба ни мясо…
                                                +1
                                                Это сугубо индивидуально для каждого, это не обьективная причина. Меня зацепил потрясающий дизайи и целостность языка.
                                                  +1
                                                  Дизайн.
                                                    –1
                                                    Нет не индивидуально, когда говорят RoR есть ассоциация с Ruby, когда говорят Go есть ассоциация routines, когда говорят Lua есть ассоциация c геймдевом, когда говорят D — ассоциации нет.
                                                      +2
                                                      Ассоциации в принципе индивидуальны. У меня в lua, например, первая ассоциация — таблицы. И уже потом геймдев.
                                                        +1
                                                        Да, и у меня ни с Ruby ни c Go нет ассоциаций, точно та же, как у вас нет с D.
                                                        +2
                                                        «Удобство C#, только на выходе не CIL, а нативный код» или Вам не нужен ответ и Вы в любом случае будете отстаивать своё мнение?

                                                        Если бы Брайт занялся инструментарием, поддержкой разных архитектур, я давным давно бы перелез с C++ на D.
                                                          +1
                                                          Кстати, D даже немного удобнее, чем C#, особенно в мелочах. Например в foreach в D можно получить текущий индекс, а не только значение. Мелочь, а приятно. И таких мелочей немало. Те же scope.
                                                      0
                                                      Удобный темплейтинг, компиляция в native код, compile time function evaluation, текстовые миксины. Вам этого мало?
                                                        0
                                                        Я бы сказал так, изюминка языка как раз в том, что он не заманивает какой-то особой изюминкой — это отлично спроектированный язык для практиков, созданный инженером, а не математиком :) Практичность, мощность и эффективность в нём ценятся выше какой-то Великой Концепции.
                                                        Это круто :)
                                                          0
                                                          Метапрограммирование. В C++11 так и нет static if.
                                                          0
                                                          И кстати, такой изюминкой может стать поддержка Native Client, или поддержка любых других модных сегодня штук. Проблема языка в том, что никто не занимается его пиаром.
                                                            0
                                                            Да, думаю обьективная проблема именно в этом.
                                                            +1
                                                            Для язык D существует FrontEnd в LLVM?
                                                            Не хватает еще статьи как линковать и использовать с С++.
                                                            Все ли сейчас возможно заменить С++ на D, например, при программировании под ARM?
                                                              0
                                                              По поводу статьи — все будет. Есть фронтэнд к gcc.
                                                                +1
                                                                Для язык D существует FrontEnd в LLVM?


                                                                Все ли сейчас возможно заменить С++ на D, например, при программировании под ARM?
                                                                Ну формально, имея ldc и gdc(фронтенд для gcc) можно собрать и под arm, но на практике еще не встречал упоминаний об успешном применении. У того же gdc в списке багов есть парочка с arm и они давно висят, до сих пор не пофикшены.

                                                                P.S. не так давно пробовал собрать gdc для android toolchain — не вышло
                                                                  0
                                                                  Пардон, ссылка скушалась… Да, фронтенд для llvm есть — LDC
                                                                0
                                                                А как насчет фрэймвроков, IDE, компиляторов и прочей инфраструктуры, делающей из абстрактного языка реальный инструмент? Расскажите?

                                                                Второй вопрос — RTTI
                                                                  +1
                                                                  В комментария к прошлой статье я уже обещал сделать статью о инструментарии. Обещал — сделаю. Может, даже следующая будет именно об этом, раз очень активно люди на это упирают.
                                                                    0
                                                                    Всем охота посмотреть вживую, прикинуть для своих задач. Многие избалованы умными IDE и привыкли пользоваться библиотеками/фреймворками для решения типовых задач — писать в «блокноте», компилировать и собирать в консоли, работать с API ОS/DE напрямую никого не заставишь даже на очень хорошем языке :)
                                                                      0
                                                                      А вот это неправда! Я на haskell программировал в gedit. И не хэлловорды, а вполне нормальные, хоть и не очень большие программы.
                                                                        0
                                                                        Да не то, чтобы даже «умными» :) Я вот visual studio при всем желании умной назвать не могу :) Привычными, скорее. Поэтому я VisualD использовал, а более «умные» плагины для Eclipse — нет.
                                                                      0
                                                                      Охохо, извините, но похоже я нашел гораздо более потрясающую воображение тему для статьи, чем скучный рассказ об инструментах :)
                                                                      Так что не уверен, что будет дальше. Однако, инструменты обязательно будут, может только не в следующий раз.
                                                                        0
                                                                        RTTI есть:

                                                                        // test.d
                                                                        import std.stdio;
                                                                        
                                                                        class Base {}
                                                                        class Deriv : Base {}
                                                                        
                                                                        void main()
                                                                        {
                                                                            Base o = new Deriv();
                                                                            writeln( typeid(o) );
                                                                        }
                                                                        

                                                                        Выхлоп:
                                                                        test.Deriv

                                                                        Или интересует что-то более конкретное?
                                                                        +1
                                                                        Пример для sort на C++ неправельный. Он не будет компилироваться, потому что auto arr = {...}; выводит тип arr как const int[]. Ну и у массива нет методов begin и end. Поэтому лучше использовать свободные функции begin/end. Учитывая всё это, правельный пример может выглядеть так:
                                                                        std::vector arr{1, 2, 3, 4};
                                                                        std::sort(begin(arr), end(arr), [](int a, int b) { return a > b && a < 42; });

                                                                        Кстати, лямбда здесь инлайнится очень даже хорошо. Лямбда разкрывается в класс с operator(). Но современные компиляторы инлайнят даже если им дать указатель на функцию, когда функция известна во время компиляции.

                                                                        Это напомнило мне одну презентацию Walter Bright'а, в которой он рассказывал про то как D лучше чем C++. У него был пример на C++ с вектором в несколько строчек, где было допущено 3 ошибки.
                                                                          0
                                                                          Форматирование полностью покоцало мой код. Попробуем еще раз:

                                                                          std::vector<int> arr{1, 2, 3, 4};
                                                                          std::sort(begin(arr), end(arr), [](int a, int b) { return a > b && a < 42; });
                                                                          
                                                                            +1
                                                                            рекомендую тэг source вместо code, он с подсветкой
                                                                            0
                                                                            Да, знаю, что неправильный, я так написал, чтобы не зашумлять код техническими деталями и сконцентрировать внимание на смысле. Как сортировать массивы я знаю :)
                                                                              0
                                                                              Кстати, если уж об этом, будь я разработчиком стандарта, я бы предложил, чтобы auto в таких случаях дедуцировало именно vecror. Было бы удобнее. Все равно все гайдлайны говорят использовать его вместо сишных массивов.
                                                                                +1
                                                                                Я не знаю, насколько тебе знаком С++11, но в этом случае (vector<int> arr = {1, 2, 3, 4}), вектор инициализируется через так называемый initialization list. Вектор не единственный класс, который можно инициализировать таким образом. Нет никаких причин, почему именно vector должен дедуцироваться в этом примере.
                                                                                  0
                                                                                  Может и нет причин, но я бы сделал именно так. А если так мыслить, то и причин дедуцировать сишный массив — не больше. Повод для вектора — тот факт, что вектор — стандартный, легковесный контейнер. Довольно удобный. В добавок, это более C++ way, чем int[].
                                                                                +1
                                                                                Нужно стараться всегда быть точным насколько это возможно. Иначе как можно доверять технической информации от человека, который принципиально неточен в деталях?
                                                                                  0
                                                                                  Я позволил себе подобную неточность потому, что статья не о C++, и вообще пример к C++ относится лишь косвенно — я хотел показать, что преимущества подхода статического вычисления аргумента над динамическим.
                                                                                –1
                                                                                А про инлайнинг: аргумент про «достаточно умный компилятор» хорошо всем известен. Суть моего сравнения в том, что в C++ это нетривиально. Вернее, не так тривиально, как в D.
                                                                                  +1
                                                                                  > в C++ это нетривиально

                                                                                  Ну, я бы так не сказал. Объекты классов с operator(), или по-другому, функторы, инлайнились всегда особенно хорошо (а лямбда в этом примере превращается именно в функтор).
                                                                                    +1
                                                                                    Особенно хорошо — это понятие относительное.
                                                                                    Кстати, а не можете рассказать, это так по стандарту она в функтор оборачивается и вообще когда это происходит? Я таких деталей не знаю — любопытно.
                                                                                0
                                                                                Те люди, которые возмущаются моим недоверием к C++, если это не повод похоливарить, а желание обьяснить мне истину — я знаю, что все замечательно инлайнится. Другое дело, что я всегда в таких ситуациях пытаюсь представить себя на месте человека, который пишет оптимизатор для уже готового компилятора. В D мне «дадут» уже гарантированно вычисленное на стадии компиляции выражение — я его проинлайню без дополнительных умственных усилий. В C++ же мне придется попотеть — как минимум доказать, что это можно инлайнить + мне самому придется вычислять код статически.
                                                                                  0
                                                                                  В C++ описание шаблонов делается в .h файле, таким образом при создании библиотек все равно можно использовать шаблонные функции, так как с библиотекой прилагается и сам .h файл. В D определение и объявление делается в одном файле. При создании библиотеки мне нужно прилагать файл с исходным кодом для использования шаблонной функции?
                                                                                    0
                                                                                    Нет. У D есть необязательный аналог .h-файлам — .di-файлы. .di-файл может содержать все объявления без кода (содержания функций). Компилятор умеет генерировать .di-файл из .d-файла.

                                                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                  Самое читаемое