Pull to refresh

Comments 75

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

// test.d
import std.stdio;

class Base {}
class Deriv : Base {}

void main()
{
    Base o = new Deriv();
    writeln( typeid(o) );
}

Выхлоп:
test.Deriv

Или интересует что-то более конкретное?
Пример для 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 ошибки.
Форматирование полностью покоцало мой код. Попробуем еще раз:

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

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

Articles