thedeemon: (office)
Dmitry Popov ([personal profile] thedeemon) wrote2013-08-25 01:18 am
Entry tags:

Language with a Bright future

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

1. Web Development in D - какие есть замечательные инструменты (в первую очередь vibe.d), как с ними все получается просто и красиво (и эффективно), благодаря грамотному использованию возможностей D.
2. Writing Testable Code in D - тут больше демонстрация умений и удобств языка.
3. Metaprogramming in the Real World - занятный experience report от компании с сотней программистов, где весь код пишется на D.

[identity profile] juan-gandhi.livejournal.com 2013-08-24 06:21 pm (UTC)(link)
Секретный язык. На accu один раз был доклад, а так всё в тайне. Митап бы организовали, что ли...

[identity profile] tretiy3.livejournal.com 2013-08-24 09:40 pm (UTC)(link)
как вам кажется: D, Rust или Go?

[identity profile] thedeemon.livejournal.com 2013-08-25 07:19 am (UTC)(link)
Go как язык наиболее идиотский, зато реализация из этих трех самая стабильная и отлаженная.
Rust выглядит наиболее обещающим и перспективным, но он сейчас наиболее недоделанный, все время там все ломают и переделывают.
D находится где-то между ними: как язык довольно приятен, реализации более-менее юзабельные, но есть еще недоделки.
Из этих трех мой выбор сейчас - D.

[identity profile] tretiy3.livejournal.com 2013-08-25 07:56 am (UTC)(link)
клево, спасибо.

[identity profile] chaource.livejournal.com 2013-08-25 09:29 am (UTC)(link)
Я посмотрѣлъ еще заключительный докладъ Александреску. Онъ сказалъ правильную вещь - во всѣхъ учебникахъ по ФП даются какъ первые примѣры три алгоритма - факторiалъ, Фибоначчи, и сортировка - которые изъ-за отсутствiя tail recursion работаютъ совершенно непрiемлемо, а вставить tail recursion означаетъ сильно усложнять изложенiе. Его рѣшенiе - допускать императивность внутрь чистыхъ функцiй - однако же мнѣ кажется педагогически неправильнымъ, т.к. оно не объясняетъ, что такое ФП. Я думаю, правильнымъ было бы вообще не использовать рекурсiю тамъ, гдѣ можно использовать библiотечныя функцiи типа fold/map/zip, т.е. опрѣделять факторiалъ какъ map (*) (seq 1 n), Фибоначчи черезъ zip, и т.д. Вообще можно рекурсiю запрѣтить, оставить только fold/map/zip - для программированiя этого будетъ почти всегда достаточно.

[identity profile] thedeemon.livejournal.com 2013-08-25 10:33 am (UTC)(link)
Верно. Я думаю, ему просто хотелось продемонстрировать D'шный подход к чистоте. Если использовать комбинаторы из стандартной библиотеки, можно было написать
long fac(long n) { return iota(1,n+1).reduce!"a*b"; }

[identity profile] alexeyk77.livejournal.com 2013-08-25 09:53 am (UTC)(link)
а по скорости компиляции, как соотносится с плюсами?

[identity profile] thedeemon.livejournal.com 2013-08-25 10:21 am (UTC)(link)
На порядки быстрее, по крайней мере у основного компилятора - DMD. Эту фичу все перешедшие с плюсов особенно отмечают.

[identity profile] xeno-by.livejournal.com 2013-08-28 07:42 am (UTC)(link)
Насчет CTFE. Какой есть хороший туториал на эту тему? Насколько полезна эта фича на практике? Спикер говорит, что у них в конторе почти никто ее не юзает. Почему?

[identity profile] thedeemon.livejournal.com 2013-08-28 06:14 pm (UTC)(link)
Туториал на эту тему сейчас не припомню.

С вызовом функций в компайл-тайме на практике редко сталкиваешься, т.к. все интересные данные программа получает уже в рантайме, а при компиляции какие данные есть? Собственные исходники разве что, так что фича эта в основном используется внутри всяких шаблонов, генерирующих код. Чтобы код удобно было генерить, нужна работа со строками, массивами и основные алгоритмические штуки из стандартной библиотеки, вот они и используются. Т.е. в целом фича, может, и мегакрутая, но замечают это лишь библиотекописатели, а при написании обычных программ ее и не замечаешь, даже если где-то внутри используемых библиотек она и работает активно.

[identity profile] xeno-by.livejournal.com 2013-08-28 06:16 pm (UTC)(link)
О, хорошо, что ты напомнил на тему строк. Не напрягает ли сборка кода на коленке? Не хочется ли временами квазицитат?

[identity profile] thedeemon.livejournal.com 2013-08-28 06:29 pm (UTC)(link)
Мне самому сборка кода из строчек ни разу не пригождалась, и в идиоматичном коде она обычно не используется. Хватает сочетания шаблонов и static if. Что-то вроде:

class MyClass(T) {
...
static if (SomePredicate!(T)) {
  Result!T my_function(T x, ...) {
    ....
  }
}
...

Тут и квазицитаты не нужны, пишешь обычный код.

[identity profile] xeno-by.livejournal.com 2013-08-28 06:30 pm (UTC)(link)
Класс, то, что я думал! Тогда следующий вопрос находится вот тут: http://thedeemon.livejournal.com/68456.html?thread=1192040#t1192040 :)

[identity profile] xeno-by.livejournal.com 2013-08-28 07:44 am (UTC)(link)
Да и вообще, как народ юзает МП в D? Какие самые типичные юзкейсы?

[identity profile] thedeemon.livejournal.com 2013-08-28 06:04 pm (UTC)(link)
Заметные примеры - регекспы и PEG-парсер, которые подготавливаются и оптимизируются в компайл-тайме.

Типичные же юзкейсы - это получить на вход некоторый заранее неизвестный тип Т, используя развитую компайл-тайм рефлексию разузнать, подходит ли он и что он умеет, и на выходе родить новый тип R, сохраняющий или преобразующий полезные свойства Т и делающий что-то свое. Например, так комбинаторы map/take/reverse/zip и т.п. сохраняют такие свойства переданных в них последовательностей как "известная длина", "клонируемость", "доступ по индексу за О(1)" и т.д. Или, например, берется на вход некоторый класс и возвращается его аналог, где методы превратились в RPC вызовы или наоборт обработчики RESTful запросов. Вся рефлексия и рождение кода происходят при компиляции, рантайм-оверхед нулевой.

Ну и всевозможная сериализация-десериализация, конечно.
Edited 2013-08-28 18:06 (UTC)

[identity profile] xeno-by.livejournal.com 2013-08-28 06:08 pm (UTC)(link)
Насколько юзают МП для дслей? А для верификации кода?

[identity profile] thedeemon.livejournal.com 2013-08-28 06:19 pm (UTC)(link)
Не знаю. Для верификации больше используют фичи design by contract (пред- и пост-условия функций, инварианты классов, сложный анализ шаблонных параметров) и встроенные в язык юниттесты.

[identity profile] xeno-by.livejournal.com 2013-08-28 06:09 pm (UTC)(link)
А еще такой вопрос. Что тебе не нравится в дишном метапрограммировании?

[identity profile] thedeemon.livejournal.com 2013-08-28 06:55 pm (UTC)(link)
Если говорить в целом, как о подходе - не знаю, надо подумать.
А если про конкретную реализацию, то:
1. Порой хочется увидеть результат раскрытия всех шаблонов. Вон в Си можно посмотреть результат препроцессора, в GHC куча всяких промежуточных представлений доступна, а тут я не знаю, как заглянуть в середину между исходником и бинарником. Воможно, в GDC или LDC что-то такое есть, не в курсе.
2. Сообщения об ошибках порой не шибко информативные. Например, если есть несколько реализаций шаблона с разными предусловиями, и ни один предикат не сработал, то шаблон не инстанциируется, при этом сообщение об ошибке может быть весьма неожиданным, приходится догадываться.
3. Если писать сложный код в ФП-стиле, где замыкание внутри замыкания замыканием погоняет, и все передаются как alias параметры во всякие комбинаторы, компилятору порой сносит крышу.

[identity profile] xeno-by.livejournal.com 2013-08-28 06:14 pm (UTC)(link)
Касательно коллекций. Вот в Скале похожая функциональность реализована на фандепах без всякого метапрограммирования. С одной стороны, получается немного awkward программировать на языке тайпклассов. С другой стороны, не нужно никакого МП, т.е. все строго на типах, без компайл-тайм рефлексии. Какой подход ты бы предпочел?

[identity profile] thedeemon.livejournal.com 2013-08-28 06:37 pm (UTC)(link)
Я не знаю, как именно это выглядит в Скале. В D подобная логика выглядит настолько straight-forward, что сложно представить что-то более подходящее. Через шаблоны и CTFE можно иметь предикаты и функции на типах произвольной сложности, а static if c ними позволяет гибко менять компоненты и свойства строящегося типа. Не нужно вводить целый под-язык тайпклассов.

[identity profile] xeno-by.livejournal.com 2013-08-28 06:49 pm (UTC)(link)
Как в D написать функцию map, которая: 1) определена для всех коллекций (листов, векторов, чего угодно, соответствующего базовому интерфейсу), 2а) на выходе выдает ту же самую коллекцию, параметризованную типом результата маппера, 2б) за исключением случая, когда маппер возвращает Boolean - в таком случае возвращаемый тип map должен быть BitVector.

Что-то типа:
map(List(1, 2), x => x) возвращает List[Int]
map(Vector(1, 2), x => x.toString) возвращает Vector[String]
map(Map(42 -> "42"), x => true) возвращает BitVector

[identity profile] thedeemon.livejournal.com 2013-08-28 07:21 pm (UTC)(link)
Это надо придумывать заново базовый интерфейс, а у меня уже третий час ночи. Вот дишный подход, он несколько отличается, в частности map получается ленивым (kinda):
http://ddili.org/ders/d.en/ranges.html
Сейчас контейнеры умеют представлять себя в виде рэнждей, с которыми работают всевозможные алгоритмы и комбинаторы. Если контейнерам еще добавить универсальный способ создаваться из рэнджей (не помню, есть ли это сейчас), то запрошенная тобой функция будет совсем простой. Завтра могу попробовать сделать пример.

[identity profile] xeno-by.livejournal.com 2013-08-28 07:39 pm (UTC)(link)
Спасибо, было бы здорово!

[identity profile] xeno-by.livejournal.com 2013-08-29 09:14 am (UTC)(link)
Слушай, я вот тут еще подумал. Можно ли на дишечных шаблонах наколбасить LINQ, т.е. чтобы шаблоны каким-то образом преобразовывали лямбды, передаваемые в map/filters/etc, в сиквел?

[identity profile] thedeemon.livejournal.com 2013-08-29 12:34 pm (UTC)(link)
Шаблоны внутрь переданных функций вроде как не могут заглядывать, но задача наверняка решается с помощью
http://en.wikipedia.org/wiki/Expression_templates
т.е. сделать лямбды не простыми, а строящими AST. С перегрузкой операторов это не так сложно.

[identity profile] xeno-by.livejournal.com 2013-08-29 03:21 pm (UTC)(link)
С виртуализацией есть свои проблемы. Например, как лифтить константы или что делать с дереференсом филдов.

[identity profile] xeno-by.livejournal.com 2013-08-28 06:29 pm (UTC)(link)
Еще один вопрос, надеюсь я не сильно утомляю. Просто весь день провел за чтением ресурсов по D, поэтому сейчас прямо вспышка интереса.

В целом, мне нравится дишный подход к метапрограммированию. Да, многие вещи довольно адхочно сделаны, но в целом все очень задорно, и, что главное, народ не стесняется МП как у нас в коммьюнити.

Конечно, когда я читал посты и тексты, в голове сразу начал работать сравнитель. С одной стороны, стратегия как в лиспах, как у нас, когда вычисления времени компиляции спрятаны за макросами, которые основаны на reflection API. С другой стороны ди, в котором есть куча разных средств CTM (как утверждает один пост, "here is the basic tool box: typeof, is, alias, static if, __traits, std.traits, CTFE."), которые можно юзать вперемешку с кодом.

Какой из этих двух стилей МП тебе больше нравится? Да, в ди некоторые вещи можно делать очень просто, но за это приходится расплачиваться пачкой фич в языке + винегретом из метауровней. Насколько это является проблемой на практике?

[identity profile] thedeemon.livejournal.com 2013-08-28 07:08 pm (UTC)(link)
Тут у меня недостаточно опыта для сравнения. Я видел много разных попыток сделать удобные лисп-стайл макросы (camlp4, nemerle, haxe, какие-то кусочки скалы...), и везде это пляски с бубном, часто отдельные фазы компиляции, отдельный синтаксис и длинное страшное слово "метапрограммирование". Отсюда и стеснение. В D же просто берешь и пишешь generic код, даже и мысль о таком длинном слове не приходит.

[identity profile] xeno-by.livejournal.com 2013-08-28 07:35 pm (UTC)(link)
Да, это меня очень впечатляет.

[identity profile] fi_mihej.livejournal.com 2013-08-28 07:15 pm (UTC)(link)
Неожиданно: D в контексте метапрограммирования и ФП. Да еще и с такими вкусными фичами, как преобразование класса для работы с RPC и REST. Крутотенечка!
А я-то, грешным делом думал, что он сдох давно.
Edited 2013-08-28 19:16 (UTC)
wizzard: (Default)

[personal profile] wizzard 2013-08-29 12:30 am (UTC)(link)
отлично! еще хочу ;)

[identity profile] xeno-by.livejournal.com 2013-09-08 10:37 am (UTC)(link)
Слушай, а как у МП в D обстоят дела с порядком применения метапрограмм?

Например, вот в этом случае могут ли раскрываемые шаблоны видеть мемберы класса C и какие именно мемберы они увидят:
class C {
  int x;
  mixin(genJsonSerializable!C); // перебирает мемберы и создает метод toJson
  mixin(genXmlSerializable!C); // перебирает мемберы и создает метод toXml
  int y;
}

[identity profile] thedeemon.livejournal.com 2013-09-08 06:29 pm (UTC)(link)
Обстоят удивительным образом. Оба миксина увидят все четыре мембера. Тестовый пример:
mixin template ToJSON(C)
{
    void toJSON()
    {
        enum mbs = [__traits(allMembers, C)]; // compile-time created array 
        writeln(mbs);
    }
}

mixin template ToXML(C)
{
    void toXML()
    {
        enum mbs = [__traits(allMembers, C)];
        writeln(mbs);
    }
}

class X {
    int x;
    mixin ToJSON!X;
    mixin ToXML!X;
    int y;
}

void main(string[] argv)
{
    auto x = new X;
    x.toJSON;
    x.toXML;
}

Выводит:
["x", "toJSON", "toXML", "y", "toString", "toHash", "opCmp", "opEquals", "Monitor", "factory"]
["x", "toJSON", "toXML", "y", "toString", "toHash", "opCmp", "opEquals", "Monitor", "factory"]

[identity profile] xeno-by.livejournal.com 2013-09-08 08:39 pm (UTC)(link)
Феноменально! А как это вообще работает? Где-нибудь есть популярное изложение раскрытия шаблонов в D?
Edited 2013-09-08 22:44 (UTC)

[identity profile] thedeemon.livejournal.com 2013-09-09 03:55 am (UTC)(link)
Не знаю. :)

[identity profile] xeno-by.livejournal.com 2013-09-08 10:44 pm (UTC)(link)
О, еще. Можно ли из темплейта прочитать содержимое внешнего скоупа? Например, чтобы в ToJSON не надо было передавать X.

[identity profile] thedeemon.livejournal.com 2013-09-09 03:52 am (UTC)(link)
Добавляем в начало
module mymodule;
и в коде пишем:
writeln([__traits(allMembers, mymodule)]);
получаем
["object", "std", "ToJSON", "ToXML", "X", "main", "D12TypeInfo_Axa6__initZ"]
Если еще вспомнить про user-defined attributes, получаем довольно могучий струмент.

[identity profile] xeno-by.livejournal.com 2013-09-09 06:37 am (UTC)(link)
Сорри, я что-то туплю. А как это поможет написать не ToJJSON!X, а просто ToJSON?

[identity profile] thedeemon.livejournal.com 2013-09-09 07:51 am (UTC)(link)
Не, это я про сильно внешний скоуп подумал - чтобы сразу весь модуль проанализировать. Если нужен только "вызывающий", то есть parent:
mixin template ToXML()
{
    void toXML() { writeln([__traits(allMembers, __traits(parent, toXML))]);   }
}

class X 
{
    int x;
    mixin ToXML;
}

Вызов toXML выводит
["x", "toXML", "toString", "toHash", "opCmp", "opEquals", "Monitor", "factory"]