thedeemon: (office)
Давно заметил, что если нужно перевести слово, вместо рыскания по словарям, дающим сразу десяток значений, часто самый адекватный перевод получается, если открыть по этому слову страницу в википедии и посмотреть, как называется эта страница на русском.



Сейчас сделал себе из этого консольный переводчик. Теперь могу в консоли так делать:
>ru meerkat
Сурикат

>ru sodium
Натрий

Вот весь исходник с парой занятных моментов:
import std.stdio, std.net.curl, std.json;

auto rus(string s) { 
  version(Windows) { import std.windows.charset, std.conv; return toMBSz(s, 866).text; }
  else return s;
}

void main(string[] argv) {
  scope(failure) return;
  if (argv.length < 2) return writeln("usage: ru word");
  get("https://en.wikipedia.org/w/api.php?action=query&format=json&prop=langlinks&lllang=ru&titles="~argv[1])
    .parseJSON["query"]["pages"].object.values[0]["langlinks"][0]["*"].str.rus.writeln;
}

Старожилы рассказывают, что в древнем наречии Visual Basic было заклинание On Error Resume Next, которое говорило в случае ошибки ее тупо игнорировать и идти дальше. Часто его вспоминаю, когда использую в D другое заклинание - scope(failure) return. Конструкция scope заворачивает код до конца скоупа в try-catch и позволяет указать некоторый код, который будет выполнен если вылетело исключение (scope(failure)), если не вылетело (scope(success)) или в любом случае (scope(exit)). Удобно для всяких RAII-подобных вещей, когда лень писать честный try-catch-finally или создавать обертку с деструктором. Так вот, если в scope(..) засунуть return, это приведет к тихому выходу из функции без дальшейшего пробрасывания исключения. В данном примере это означает, что если не удалось сделать запрос или найти в ответе искомое, программа завершается молча.
Другой забавный момент - как точки помогают писать в бесточечном стиле. :) За счет активного использования UFCS (universal function call syntax), методов и свойств, в теле main вообще ни одной переменной. В Окамле, F# или Elm'e это было бы много слов, разделенных |>, а в хаскеле - точками или $ да еще в обратном порядке. D-ивный вариант бесточечной записи через точки мне сейчас субъективно симпатичней.
Если вдруг кому нужно, виндовый бинарник можно взять тут, а можете и на свой любимый язык переложить, там логики-то на две строчки.
thedeemon: (office)

железные сапоги от граблей! (грабель?)

one more )
thedeemon: (office)
Как вы, вероятно, знаете, в мире Windows до сих пор COM живее всех живых, и поскольку его должно быть можно использовать и из Си, работа с ошибками там построена на базе кодов возврата: функции обычно возвращают HRESULT, целое число, которое 0 если все ок, отрицательно при ошибке и положительно, если это как бы не ошибка, но и не совсем полный успех. Ошибки надлежит не игнорировать и не пропускать, как-то обрабатывать, однако очень часто вся обработка сводится к тому, чтобы прекратить выполнение текущей ф-ии и передать эту ошибку наверх. В языках вроде Go это превращается в код вида

err := obj1.Action1(a, b)
if err != nil {
    return err
}
err := obj1.Action2(c, d)
if err != nil {
    return err
}
err := obj2.Action3(e)
if err != nil {
    return err
}

и т.д. Каждая строчка превращается в четыре, в лучших традициях


Read more... )
thedeemon: (office)
Недавно спрашивали про использование компайл-тайм рефлексии в D, покажу пример. Есть такая знаменитая работа про бесплатные теоремы, где рассказывают о радостях генериков, ничего не знающих о том, что за типы у них в параметрах. Это часто позволяет по одному типу функции понять, что она будет делать, т.к. вариантов что ей делать у нее немного, ибо кроме передачи туда-сюда аргументов она, не зная ничего о них, почти ничего и не может с ними делать. Но что нам достается бесплатно мы часто не ценим, поэтому стоит обратить взор на обратный подход, где теоремы платные, по 72-139 рублей (без НДС) за штуку, в зависимости от набора используемых аксиом (с аксиомой выбора дороже!). Я имею в виду случай, когда генерик-функция, получая на вход тип Т, может во время компиляции позадавать про Т разные вопросы, что там у него внутри и что с ним можно делать, и в зависимости от ответов делать то или иное. Read more... )
void receive(Reactor)(File pipe, Reactor r) {
    uint[2] headerBuf;
    auto header = pipe.rawRead(headerBuf);
    if (header.length < 2) throw new CommException("eof"); 
    switch(header[0]) {
        foreach(T; MessageTypes!Reactor) {
            case MsgTypeHash!T:
                auto data = header[1] > 0 ? pipe.rawRead(new ubyte[header[1]]) : null;				
                return r.react(decerealise!T(data));
        }
        default: ... // обработать случай неизвестного сообщения
    }       
}

Read more... )
thedeemon: (office)
Коробочный продукт, над которым сейчас работаю, подошел к стадии частной беты. Пишется он на D с гуем на DlangUI. Это довольно обширная кроссплатформенная библиотека (под 50к строк), и, линкуясь с ней, у меня не было уверенности, что в мой бинарник не попадет какой-нибудь ненужный слон. Сейчас главный ехе-шник у меня имеет размер 2.2 МБ. Вспомнил про онлайн утилиту Владимира Пантелеева TreeMap Viewer, сунул туда свой .map файл и получил красивую интерактивную карту. Довольно познавательно. Слонов не обнаружил, есть некоторое количество зайцев, но они небольшие, не жалко. Оказалось, что из тех 2.2 МБ две самых больших части, почти одинакового размера по 800+ КБ, это dlangui и Phobos, стандартная библиотека. Весь рантайм занял около 380 КБ, из которых GC всего 24, а остальное про исключения, рефлексию, массивы, форматирование строк, ну и C runtime, конечно. Очень много кода посвящено поддержке юникода. В целом, я доволен. Не то, чтобы размер имел большое значение, но все же приятно, когда общий размер распространяемых бинарников получается на порядок меньше чем с каким-нибудь Qt при схожей функциональности. И к вопросу о "языках с рантаймом".

ping pong

Nov. 20th, 2015 05:17 pm
thedeemon: (office)
Впервые дошли руки поиграться с файберами/корутинами. Кроме меня в игре два игрока: каждый из них ловит мяч (целое число), прибавляет к нему единицу и посылает другому игроку. Если число доросло до 100 000, посылает его главному треду и завершается. Главная ф-я создает двух игроков, знакомит друг с другом, запускает мяч и ждет пока оба не пришлют свои результаты. Показанный ниже код, в зависимости от (не)переданного аргумента командной строки, устраивает такую игру либо с игроками в отдельных параллельных тредах ОС, либо в разных корутинах в пределах одного треда.

Read more... )

На моем ноуте вариант с тредами ОС работает ~520 мс, а вариант с корутинами - 137 мс. При этом код фактически один и тот же, и, как я понимаю, реализация мэйлбоксов у них одна - т.е. в варианте с корутинами имеют место ненужные блокировки, можно было бы быстрее. Чуть разный код получился у ждущего треда - в одном случае идет блокирующее ожидание сообщения, в другом - неблокирующее + переключение. Но сам факт, что можно легко выбирать между разными скедулерами или задействовать свой, гибридный, доставляет. Было бы занятно сравнить времена и реализации с другими языками.

Upd: версия на Go отрабатывает за 35 мс.

Upd2: ее аналог на D (с самодельными типизированными каналами вместо универсальных мэйлбоксов и только корутинами) работает 24 мс.
thedeemon: (office)
Микола, бачив як С++ники xs.writeln пишуть?
std::copy(std::begin(xs), std::end(xs), std::ostream_iterator<unsigned char>(std::cout));
std::cout << std::endl;

навеяно
thedeemon: (office)
Занимался тут исследовательским/экспериментальным кодом, а там получилось, что одни и те же действия делаются с двумя похожими наборами данных (горизонтальные и вертикальные координаты), большую часть времени независимо. И захотелось вместо того, чтобы писать одинаковый код и следить, как бы не передать куда иксы вместо игреков, просто лифтануть имеющиеся функции, чтобы они работали сразу с парами значений.
На хаскеле для этого достаточно описать, каким образом диагональный функтор (гомогенные туплы) является аппликативным. Что-то вроде:

data Pair a = P a a 
  deriving (Functor, Show)

instance Applicative Pair where
  pure x = P x x
  P fa fb <*> P xa xb = P (fa xa) (fb xb)


(вот только на самом деле не хочется определять свой тип, было бы здорово обычные туплы (а,а) там использовать)

Но у меня тот проектик был не на хаскеле, а на D. Там подход чуть иной. Сперва как оно выглядит в использовании:

unittest {
    int  f1(string s)                { return s.length; }
    int  f2(int x, int y)            { return x + y; }
    bool f3(int a, string b, bool c) { return c && (a <= b.length); }

    auto r1 = tmap!f1( tuple("aa", "bbb") );
    writeln(r1); // (2,3)

    auto r2 = tmap!f2( tuple(10,20), r1 );
    writeln(r2); // (12, 23)

    auto r3 = tmap!f3( r2, tuple("one", "five"), tuple(true, false));
    writeln(r3); // (false, false)
}


А вот как это реализовано:

alias Dbl(T) = Tuple!(T,T);
alias Double(Ts...) = staticMap!(Dbl, Ts);

auto tproject(int pos, Ts...)(Ts args) {
    enum projStr = iota(args.length).map!(i => format("args[%s][%s]", i, pos)).join(", "); 
    return mixin("tuple(" ~ projStr ~ ")");
}

auto tmap(alias f)(Double!(Parameters!f) args) {
    return tuple(f(args.tproject!0.expand), f(args.tproject!1.expand));
}


По шагам:
tmap получает некоторую функцию f в качестве compile-time-known параметра, и набор рантайм-аргументов args, типы которых вычисляются как Double!(Parameters!f). Parameters - это типовая ф-я из стандартной библиотеки, она возвращает список типов аргументов переданной ей ф-ии. К этому списку мы применяем типовую функцию Double, которая просто маппит список, преобразуя каждое значение функцией Dbl, которая всякий Т превращает в Tuple!(T,T), где Tuple - контейнер из стандартной библиотеки. В результате, например, если исходная функция f принимала аргументы типов (string, bool, int[]), то tmap!f будет принимать аргументы типов (Tuple!(string, string), Tuple!(bool, bool), Tuple!(int[], int[])).
Возвращает tmap, понятное дело, тоже тупл, полученный применением исходной ф-ии к соответствующим элементам входных туплов. Для этого ей надо разделить набор входных туплов на два набора - левые и правые половины оных. Для такой проекции из набора туплов в набор их частей описана ф-я tproject. В принципе, ее наверняка можно реализовать более чистым образом, но я использовал тупую кодогенерацию. Если args это набор туплов, то args[0] это первый тупл, а args[0][0] это первое значение из первого тупла. Т.е. чтобы взять все первые значения нам нужен tuple(args[0][0], args[1][0], args[2][0]...). Такая строчка генерится в компайл-тайме в первой строке tproject и тут же вставляется во вторую строчку, так получается нужное значение.
А, насчет синтаксиса. args.tproject!0 это (благодаря Universal Function Call Syntax) то же самое, что tproject!(0)(args), т.е. передаем компайл-тайм аргумент 0 (это будет pos), и рантайм-аргумент args, а второй компайл-тайм аргумент Ts - это список типов args, он выводится/извлекается автоматически. Три точки говорят, что это не одно значение, а набор значений (несколько аргументов). Чтобы развернуть библиотечную структуру Tuple в набор передаваемых значений, делается .expand.
Вот и все, дешево и сердито. А как такое делается на вашем языке программирования?
thedeemon: (office)
Посмотрел тут свежее выступление одного из активных пильщиков Идриса: David Christiansen - Coding for Types: The Universe Patern in Idris. Он там рассказывает, что вот в языках с зависимыми типами у нас типы вроде как первоклассные, можно их как обычные значения создавать, передавать и использовать, но вот паттерн-матчить по ним не положено - это отнимет бесплатные теоремы (id может начать возвращать 42 для всех натуральных аргументов, например) и помешает стиранию этих типов из рантайма. Не положено (надо бы сказать "нельзя", но по крайней мере несколько версий назад кое-какой простой матчинг по типам там работал), но порой очень хочется. Для этого есть дизайн паттерн: The Universe Pattern, заключающийся в том, что для интересующего нас набора типов мы делаем тип данных, их кодирующий (аки AST), где хотим матчимся по нему, а когда нужны сами типы, то просто отображаем их из этой кодировки. Например, есть у нас таблички, где поля могут быть строками, целыми или вещественными числами. Описываем это алгебраиком:
data Column = TEXT | REAL | INTEGER

и сразу делаем отображение в "настоящие" типы:
interpCol : Column -> Type
interpCol TEXT -> String
interpCol REAL -> Double
interpCol INTEGER -> Integer 

Надо превратить поле одного из этих типов в строку? Пожалуйста:
printCol : (c : Column) -> interpCol c -> String
printCol TEXT    x = x
printCol REAL    x = doubleToString x
printCol INTEGER x = intToString x  

Тут функция с двумя аргументами, где тип второго зависит от значения первого, т.е. зависимые типы как они есть. Можно вызвать printCol TEXT "hi", а можно printCol INTEGER 77, но printCol TEXT 77 уже не скомпилируется, будет ошибка типов.
Read more... )
thedeemon: (office)
Андрей Александреску, который когда-то непоправимо и бесповоротно изнасиловал мозги С++ программистов книгой об изощрениях на С++ шаблонах, выступил на днях как тролль 78-го уровня:

Доклад с названием Generic Programming Must Go содержит фразы вроде "Generic Programming is fail" и "Concepts are fail". Но не обольщайтесь, он не содержит призывов переходить на Го, где нет генериков. Речь о другом.
Read more... )
thedeemon: (office)
Увидел на днях у afiskon'a гостевой пост с реализацией на Go игрушечной задачки про подбор пароля по его MD5 хэшу. По условию в пароле 5 символов из алфавита 0..9a..z, перебор надо распараллелить по ядрам. Там же есть ссылки на предыдущие инкарнации (на хаскеле и эрланге), в них я сейчас не вчитывался. А еще тот же пост увидел eao197 и сделал свой вариант на C++ с использованием его любимого SObjectizer'a. Я решил присоединиться к флэшмобу и сделал вариант на D. Варианты на С++ и D - прямые переводы с Го, поэтому за объяснением рекомендую смотреть пост про гошный вариант, там подробно расписано. Полные исходники:
Go (84 lines)
C++ (155 lines)
D (48 lines)

На моем ноуте с i3 и двумя ядрами по два гипертреда вариант на Go отрабатывает за 13 секунд, вариант на D - за 6 секунд. Оба грузят проц под 100%, имеют по 5 потоков и памяти едят меньше 2 мегов. Что занятно, на этом примере получился очень резкий контраст между компиляторами D: при сборке LDC получается вдвое быстрее Го, а при сборке DMD - вдвое медленнее.

Что касается кода, в варианте на С++ просто почерк очень размашистый, при другом форматировании было бы заметно компактнее, но и синтаксического шума, конечно, тоже заметно больше чем в остальных вариантах. В Го рабочие потоки получают задания и посылают ответ через каналы, которые они шарят меж собой. В D использован модуль из стандартной библиотеки std.concurrency, где нет в явном виде каналов, а вместо этого потоки могут посылать друг другу сообщения аки процессы в эрланге. В частности, у меня тут главный поток создает массив Tid'ов (индентификаторов потоков), запустив нужное число воркеров, а потом раскидывает им задания:

workers[i % $].send( Part( pass.idup, b ) );


$ внутри [] означает длину массива, так номер задачки i мапится на номер рабочего. Встречающиеся в коде .idup и .dup создают иммутабельную и мутабельную копии массива соответственно (посылать между потоками без глубокого копирования разрешается лишь иммутабельные данные).

У каждого потока свой почтовый ящик, откуда он полученные сообщения достает функцией receive, передавая ей разные функции-обработчики, receive по типу этих обработчиков выбирает подходящий, кому отдать данные из сообщения. Соответственно, главный цикл рабочего потока, по мотивам гошного варианта, выглядит
while(true)	
  receive( (Part p) { 
    ... тут вложенная функция, вызывающаяся при получении структуры Part


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

Такое вот баловство.
thedeemon: (office)
Добавил тут в одну программку простейшую конфигурацию: именованные параметры, загружаемые при первом обращении из текстового файла, и имеющие значения по-умолчанию на случай если файл конфигурации отсутствует или не содержит нужного значения. Потом заметил, что имена всех параметров указаны в исходниках, т.е. известны во время компиляции. А значит было бы неплохо прямо при компиляции убеждаться, что все запрашиваемые имена параметров верные, т.е. для каждого запрашиваемого параметра описано значение по-умолчанию, и нигде не будет ненароком запрошено что-нибудь неизвестное.

Делается просто. Добавляем еще одну пару скобок к функции получения значения из конфигурации, делая передаваемое имя параметра не рантайм-, а компайлтайм аргументом. Ассоциативный массив с ответами по-умолчанию делаем компайл-тайм константой (волшебное слово enum). В начало функции добавляем static if с проверкой (осуществляемой при компиляции) на наличие запрашиваемого имени в том ассоц.массиве. Если не найдено, говорим ругаться при компиляции такими-то словами. Все.

string getOptionString(string name)() {
    static if (name !in defaults) {
        static assert(0, "unknown option name: " ~ name);
    }
    auto ops = getOptions();
    return ops[name];
}

private:

enum defaults = ["quantity": "large", 
                 "quality" : "high"];
...


Теперь если допустить очепятку и написать
string q = getOptionString!("quanlity");

то программа не компилируется, а компилятор пишет
options.d(5): Error: static assert  "unknown option name: quanlity"
main.d(210):        instantiated from here: getOptionString!"quanlity"


Мелочь, а приятно. За такие вот в том числе и люблю D.

дебаг

Oct. 21st, 2014 01:30 pm
thedeemon: (office)
Вчера с прикольным багом столкнулся. Маленький веб-сервис, принимает запросы, отвечает JSON'ом. Посылаю запрос - отвечает, все хорошо. Посылаю еще раз - еще раз тот же ответ. Так несколько раз успешно, потом бац - при том же запросе вдруг Access Violation. Потом на тот же запрос опять успешно отвечает все тот же ответ. Т.е. при одних и тех же данных то работает, то не работает. Заметил, что чем больше обрабатывается данных, тем чаще не работает. Отладчик в поиске причины оказался не очень полезен: ну да, где-то в RTTI данных (которые автоматически генерятся компилятором) внезапно оказывается неожиданный null, и рантайм на него натыкается, но почему там null? Пришлось применить Метод Божественного Озарения™. Оказалось, я недавно перевел свои робингудские хэш-таблицы на использование библиотечного std.container.Array, который данные вне GC-кучи хранит и сам менеджит. А в нем оказался баг: при определенных условиях он забывал оповестить GC об имеющихся в массиве указателях, GC приходил и собирал те объекты, указатели в массиве оказывались протухшими. Главное в применении такого метода отладки - отойти подальше от монитора и клавиатуры.
thedeemon: (office)
Есть вещи, которые совершенно тривиальны в динамически типизированных и в зависимотипизированных языках, но вот в куче промежуточных - привычных нам статически типизированных - или не делаются вовсе, или требуют страшных слов на букву "м" (нет, не монад). К счастью, бывают исключения. Пара примеров из свеженаписанного кода:

1.
...
alias types = TypeTuple!(bool, int, double, char, string, 
                         TestStruct!false, TestStruct!true, TestClass!false, TestClass!true);
foreach(t1; types)
    foreach(t2; types) 
        testRB!(t1, t2, false)(num);

Тут функция вызывается с 81 комбинацией типов.

2.
alias MakerType(T) = T delegate();
void testHashesHisto(K, V, Hs...)(size_t num, staticMap!(MakerType, Hs) makers) {
...
    foreach(i, H; Hs) {
        auto h = makers[i]();
        measure("# " ~ H.stringof ~ ".make_histo", (){
        ...

Эта функция помимо целого числа num принимает произвольное количество функций, производящих значения указанных при ее вызове типов. Тип каждой конкретной ф-ии из тупла makers формируется применением type-level ф-ии MakerType к переданному типу из тупла Hs. В теле ее имеется цикл, проходящий по этому набору функций, на разных итерациях вызываются разные ф-ии из этого набора, получаются значения разных типов, и дальше используются как сами эти значения, так и имена их типов.

Что приятно, что тут не возникает раздумий о макросах и метапрограммировании, все пишется довольно естественно и органично, это просто код.
thedeemon: (office)
Хорошее выступление с недавно прошедшей конференции:


Компайл-тайм интроспекция + компайл-тайм кодогенерация = big win. Я правильно понимаю, что в Rust (а также С++, Go, Swift) ничего подобного нет и не предвидится?
thedeemon: (office)
Некоторое время назад сделал себе трекер аллокаций в D и обнаружил, что замыкания там реализованы несколько не так, как я ожидал, а довольно остроумным способом. Давайте для примера опишем простую ФВП и попередаем ей из одной функции всякие лямбды, захватывающие разные переменные из окружения, да по нескольку раз :


int twice(int delegate(int) f, int x) { return f(f(x)); } 

void fun() 
{
    int x = 10, y = 100;
    byte[40] arr;
    double z = 55;
    foreach(i; 0..3)
        twice(n => n + arr[8] + x, i).writeln;
    foreach(i; 10..13)
        twice(n => n + y++, i).writeln;
    foreach(i; 20..23)
        twice(n => n + y + arr[2], i).writeln;
}

Теперь вызовем fun(). Как думаете, сколько тут будет сделано аллокаций и сколько всего памяти под них будет запрошено? (компиляем в 32 бита)
Read more... )

Chocolatey

Jan. 2nd, 2014 01:19 pm
thedeemon: (office)
Недавно узнал про Chocolatey - это виндовый пакетный менеджер а-ля apt-get. C ним установка всякого софта и его зависимостей делается одной командой навроде "cinst minecraft", "cinst sublimetext2", "cinst virtualdub", "cinst putty", "cinst dropbox" или "cinst Erlang16". Сейчас там почти 1500 пакетов. Как и положено творениям адептов командной строки, discoverability у самого инструмента и его сайта ниже плинтуса: чтобы узнать, какие там вообще есть пакеты, предлагается листать список из 1460 позиций последовательно по 20 штук на странице. Поэтому я, пользуясь их API, сгенерил список тэгов и список имен пакетов, так хотя бы какая-то обозримость появляется. Исходник самой генерилки (99 строк) здесь.
thedeemon: (office)

Пусть совсем простенький и сырой, но все ж таки REPL для D.
thedeemon: (office)
Есть в D штука, представляющая из себя существующий исключительно на фазе компиляции гетерогенный список, внутри которого могут быть типы и простые значения (всякие числа, строки, булевые..). Изначально возник для манипуляций параметрами шаблонов. Для него доступен набор привычных операций: map, filter, итерация, взятие слайса, обращение по индексу и т.п. И поскольку элементы такого списка могут быть разных типов, сделано так, что итерация по нему обычным foreach всегда раскрывается. Это, в частности, дает простой способ гарантированно анроллить циклы без копипасты. А в сочетании со вставкой кода через mixin() позволяет делать такое:
alias funNames = TypeTuple!("sin", "cos", "tan", "asin", "acos", "atan", "exp");
...
  switch(fn) {
    foreach(s; funNames) 
      mixin("case \"" ~s~ "\" : return new Real(" ~s~ "(x.asReal));\n");
    ...
  }


Что компилятор раскрывает в
  switch(fn) {
    case "sin" : return new Real(sin(x.asReal));
    case "cos" : return new Real(cos(x.asReal));
    case "tan" : return new Real(tan(x.asReal));
    case "asin" : return new Real(asin(x.asReal));
    ...


Мелочь, а приятно.

Profile

thedeemon: (Default)
Dmitry Popov

September 2017

S M T W T F S
     12
3456789
10111213141516
17181920212223
24252627282930

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Sep. 26th, 2017 07:20 am
Powered by Dreamwidth Studios