Top.Mail.Ru
? ?

Entries by tag: d

DConf

Недавно обнаружил, что вероятно попаду в этом году на ежегодную конференцию по языку D. Года 3-4 назад ее пару раз хостил у себя Фейсбук, последние пару лет она проходила в разных городах Германии, где крупные местные D-юзеры ее хостили, а в этом году таким юзером-хостером выступает компания, где я теперь работаю, и потому конференция пройдет в Лондоне.



В прошлом году keynote speaker'ом позвали Одерского, он там что-то про Скалу сказывал.

Read more...Collapse )
В этом году таким спикером будет мой шепелявый начальник, то еще испытание для ушей слушателей. :)

Tags:

задачка с типами

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

foobar :: Int -> String -> String -> String
foobar a b c = if a > 0 then b++c else c++b

fun1 :: Double -> Int -> String
fun2 :: String -> Int
fun3 :: Int -> Double -> Double -> Double
и т.д.

Есть "динамический" тип - сумма вышеназванных, простой алгебраик

data Var = Integer Int | Number Double | Str String

и нужно реализовать такую функцию callFun, которая получает список этих вот Var'ов и одну из ф-й выше, и вызывает переданную функцию с переданными аргументами, распаковав их из списка алгебраиков в конкретные типы. Если, конечно, переданный список значений подходит по типам и количеству к вызываемой ф-ии. Ну а не подходит - так выдать ошибку. Чтобы можно было вызвать ее навроде

callFun foobar [Integer (-3), Str "bb", Str "cc"]

и вернуть ее результат. По сути это такой FFI для интерпретатора.
Как бы вы такое сделали на вашем любимом статически типизированном ЯП?

Вот как решение выглядит на D. Сначала покажу результат, как оно выглядит в использовании:

union MyTypes {
    int integer;
    double number;
    string str;
}

alias Var = Sum!MyTypes;

string foobar(int a, string b, string c) {
    return a > 0 ? b~c : c~b;
}

double nsqrt(double x, int n) {
    import std.math : pow;
    return pow(x, 1.0/n);
}

void main() {
    Var a = -3;
    Var b = "bbb", c = "ccc";
    callFun!foobar([a,b,c]).writeln; // выводит cccbbb

    [Var(256.0), Var(4)].callFun!nsqrt.writeln; // выводит 4
}


По мотивам библиотечки taggedalgebraic, описываем типы для суммы в виде union'a. Конструктор типов Sum берет такой юнион и делает из него discriminated union, структуру, где кроме исходного union'a еще тэг, дискриминатор, сделанный типом-перечислением, причем имена тэгов берутся из имен полей данного юниона.

struct Sum(U) {
    alias Names = AliasSeq!(__traits(allMembers, U));
    mixin(`enum Tag {` ~ [Names].join(", ") ~ `}`);
    U data;
    Tag tag;

    this(T)(T x) {
        enum name = Names[staticIndexOf!(T, Fields!U)];
        __traits(getMember, data, name) = x;
        tag = __traits(getMember, Tag, name);
    }
}


Генерик конструктор отыскивает нужный тип в юнионе и устанавливает нужное значение тэга. Теперь мы умеем конструировать Var'ы, надо еще научиться их использовать. Обычно в библиотечных реализациях алгебраиков и вариантов делают ф-ю visit, которая берет разные обработчики для разных типов данных и вызывает один из них, подходящий по типу текущему хранимому значению. Мы же пойдем чуть другим путем: пусть передается всего один обработчик в виде полиморфной лямбды, куда будут попадать значения разных типов, а она уже сама разберется что с ними делать.

auto use(alias fun, A)(A a) {
    final switch(a.tag)
        foreach(name; A.Names)
            case __traits(getMember, A.Tag, name):
                return fun(__traits(getMember, a.data, name));
}

Тут мы делаем switch по тэгу, а все возможные варианты тэгов у нас перечисляет foreach по компайл-тайм списку их имен, хранящемуся в переданном алгебраике А. Такой цикл по компайл-тайм списку разворачивается при компиляции, так что на каждой итерации получается своя ветка свича, в разных ветках идет обращение к разным полям юниона, значение извлекается по имени через __traits(getMember), при этом на разных итерациях цикла (ветках свича) у нас разный тип этого значения, и оно передается в хэндлер fun.
Теперь уже можно реализовать callFun:

auto callFun(alias fun)(Var[] args) {
    alias PS = Parameters!fun;
    assert(PS.length == args.length, "Error: expecting " ~ PS.length.text ~ " arguments.");
    Tuple!PS params;
    foreach(i, T; PS)
        args[i].use!((x) {
            static if (is(typeof(x)==T)) params[i] = x;
            else throw new Exception("type error: passing " ~ typeof(x).stringof
                                ~ " instead of " ~ T.stringof);
        });
    return fun(params.expand);
}


callFun получает на вход какую-то ф-ю fun (известна в момент компиляции) и массив из Var'ов.
В первой строчке получаем компайл-тайм список PS типов агрументов переданной функции.
Если по длине не совпадает с переданным набором значений - бросаем ошибку.
Заводим тупл params, содержимое которого по типам совпадает с типами аргументов переданной ф-ии. Т.е. если fun принимает string и int, то params будет иметь тип Tuple!(string, int).
Проходимся разворачивающимся в компайл-тайме циклом по этому списку типов и заодно по массиву переданных Var'ов:
для очередного переданного значения args[i] типа Var (наш алгебраик), передаем его в use вместе с лямбдой, которая получит содержащееся внутри args[i] значение уже конкретного типа. Внутри use будет скомпилирована попытка применения лямбды ко всем возможным типам, которые могут хранится в том алгебраике. Поэтому внутри лямбды мы смотрим на тип аргумента - typeof(x) - и если он совпадает с нужным типом из списка типов аргументов fun, то значит это значение x может быть записано в тупл params. Например, если PS это (string, int), и в массиве Var'ов args элемент с индекcом 1 содержит внутри целое число, то use, сделав switch по тэгу, перейдет в ветку про int и передаст целочисленное значение из юниона в лямбду. Когда оно будет передано в эту лямбду, мы сможем его записать в params[1]. Если же в args[i] было передано значение не того типа, сработотает другая ветка свича по тэгу, то когда то значение попадет в эту лямбду, static if выведет на альтернативную ветку, где мы бросим исключение - переданное значение по типу не подошло.
Ну вот, успешно заполнив таким образом тупл params, где уже сидят значения конкретных типов, осталось вызвать требуемую ф-ию с этими значениями, что и делается в последней строчке. Вот и все!
Несколько строчек, и получился универсальный код, который априори вообще не знает, с какими алгебраиками, какими наборами типов ему придется работать, и работает с любыми. При этом тут нет никакой рантайм рефлексии помимо одного switch'a по тэгу, что мы написали явно. Все проверки вроде typeof(x)==T делаются при компиляции, в рантайме их нет, все имеющиеся циклы тоже разворачиваются в компайл-тайме. В требуемую функцию передаются только правильные типы, это проверяется статически. Но вот что интересно, некоторые части программы не подпадают ни под обычное определение статической типизации, ни под динамической. Тип переменной x в callFun нельзя так просто рядом написать карандашом, как это предполагает статическая типизация. Но и динамическим в обычном смысле он не является. Вот если все вызовы callFun с разными ф-ями и все циклы внутри развернуть, получится уже вполне код уровня привычных статически типизированных языков, где каждой переменной можно статически сопоставить какой-то тип. Такое ощущение, что шаблонов/макросов в типизированных языках не хватает какого-то своего раздела в теории типов / PLT. Когда один терм в исходнике у нас обозначает несколько разных термов после раскрытия шаблона, и у них разные типы.

Tags:

LDC

Сегодня узнал, что в андроидном Termux'e можно сказать "apt install ldc" и можно прям на телефоне писать на D:

До этого у меня там уже стояли gcc, python и ruby. Там же есть vim, он уже знает про все эти языки. Пока что единственное применение было - написал солвер для одной игрушки.

Tags:

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



Сейчас сделал себе из этого консольный переводчик. Теперь могу в консоли так делать:
>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-ивный вариант бесточечной записи через точки мне сейчас субъективно симпатичней.
Если вдруг кому нужно, виндовый бинарник можно взять тут, а можете и на свой любимый язык переложить, там логики-то на две строчки.

Tags:

morning fun


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

one moreCollapse )

Tags:

Error-checking smart pointer

Как вы, вероятно, знаете, в мире 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...Collapse )

Tags:

Недавно спрашивали про использование компайл-тайм рефлексии в D, покажу пример. Есть такая знаменитая работа про бесплатные теоремы, где рассказывают о радостях генериков, ничего не знающих о том, что за типы у них в параметрах. Это часто позволяет по одному типу функции понять, что она будет делать, т.к. вариантов что ей делать у нее немного, ибо кроме передачи туда-сюда аргументов она, не зная ничего о них, почти ничего и не может с ними делать. Но что нам достается бесплатно мы часто не ценим, поэтому стоит обратить взор на обратный подход, где теоремы платные, по 72-139 рублей (без НДС) за штуку, в зависимости от набора используемых аксиом (с аксиомой выбора дороже!). Я имею в виду случай, когда генерик-функция, получая на вход тип Т, может во время компиляции позадавать про Т разные вопросы, что там у него внутри и что с ним можно делать, и в зависимости от ответов делать то или иное. Read more...Collapse )
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...Collapse )

Tags:

полюбоваться срезом

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

Tags:

ping pong

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

Read more...Collapse )

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

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

Upd2: ее аналог на D (с самодельными типизированными каналами вместо универсальных мэйлбоксов и только корутинами) работает 24 мс.

Tags:

"Modern C++"

Микола, бачив як С++ники xs.writeln пишуть?
std::copy(std::begin(xs), std::end(xs), std::ostream_iterator<unsigned char>(std::cout));
std::cout << std::endl;

навеяно

Tags:

Profile

office
thedeemon
Dmitry Popov

Latest Month

April 2026
S M T W T F S
   1234
567891011
12131415161718
19202122232425
2627282930  

Syndicate

RSS Atom

Comments

  • thedeemon
    7 Apr 2026, 19:22
    Спасибо, мне интересно! При случае напишу про ЯП хобби-проект, в котором я участвую, но там до системы типов дело ещё не дошло; я довольно мало времени в неделю ему посвящаю, увы.
  • thedeemon
    6 Apr 2026, 19:20
    Да, сейчас сделано на Racket, но планируется версия на самом себе с компиляцией в WebAssembly.
  • thedeemon
    6 Apr 2026, 19:19
    Функциональный язык с хаскелеподобным синтаксисом, статической типизацией, выводом типов, но не чистый: никаких монад и приседаний для эффектов. Свобода действий в духе Окамла или F#: захотел сделать…
  • thedeemon
    6 Apr 2026, 19:04
    А, на Racket, наверно. Я послал приглашение присоедениться на LinkedIn - на всякий случай.
  • thedeemon
    6 Apr 2026, 18:55
    Здорово - глядя на do-нотацию, наверно - some kind of pure functional, с хорошим type inference? Транслируется в Python или Swift, а на чём транслятор написан? Подожду описания!
Powered by LiveJournal.com
Designed by Lilia Ahner