?

Log in

No account? Create an account
В поиске...
ОтраЖЖение жизни
Обновление DTL 
4-ноя-2012 06:03 pm
UR-QUAN

Сегодня на моём сайте произошло значительное обновление DTL-преобразователя, благодаря которому DTL стал полноценным языком программирования, и мне будет не стыдно отвечать на любые вопросы по нему.

Поскольку я не отчитывался перед вами о предыдущем обновлении, состоявшемся в сентябре, сегодня постараюсь осветить оба.

1. Итак, язык включает в себя следующие новые конструкции.

1.1. Точка останова как языковое средство (аналог Debugger.Break() в .NET). Инструкция [debug] позволяет остановить выполнение преобразователя в выбранной точке и проинспектировать текущее состояние преобразователя.

Данное нововведение очень сильно облегчило поиск ошибок в текстах преобразований. До этого приходилось двигаться по шагам до нужного момента (в котором возникала ошибочная ситуация). А момент этот после начала преобразования мог возникать очень нескоро.

1.2. Блочные определения функций. Вместо того чтобы при определении многострочной функции описывать её имя на каждой строке, теперь можно записать её имя лишь вначале определения, а все составляющие её правила заключить в круглые скобки (пример см. в разделе 1.4).

1.3. Несколько улучшений, сокращающих запись унификации:

1.3.1. Теперь вместо [X=]. или [X=]{} можно просто писать [X]. Переменная, не получившая значения, автоматически связывается с первым элементом входных данных.

1.3.2. Вместо [X=]_ можно написать [:X]. Это обеспечивает привязку переменной ко всем входным данным.

1.3.3. Комбинированная версия - [H:T] - есть не что иное, как общепринятая в функциональных языках возможность расщеплять список на голову и хвост. Поддерживаются также конструкции вида [A:B:C:...:T].

1.3.4. В определении функции можно не описывать переменные, привязываемые к входным аргументам целиком; к таким неявным переменным можно просто обращаться по индексу:

add := _, _ => [$0 + $1] вместо бывшего ранее add := [A=]_, [B=]_ => [A + B]

1.3.5. Также параметры функции в случае 1.3.4 можно не указывать вообще - их число будет выведено автоматически:

add := => [$0 + $1].

1.4. (!) Новый формат оформления текстов на DTL: параметры функции и стрелки преобразований выравниваются друг относительно друга. И если раньше одна из функций в примерах выглядела вот так:

print := 0, 0+ =>
print := 0, 0[DL=](..) => @(str10, [DL])
print := 0, [H=].00 => @(str100, [H])
print := 0, [H=].[DL=](..) => @(str100, [H])' @(str10, [DL])
print := 0, [DL=](..) => @(str10, [DL])
print := 0, [L=]. => @(str1, [L])

print := 1, 0+ =>
print := 1, [N=](.*1.)$ => @(print, 0, [N])' тысяч
print := 1, [N=].*1$ => @(print, 0, [N]0)' одна' тысяча
print := 1, [N=].*2$ => @(print, 0, [N]0)' две' тысячи
print := 1, [N=](.*[L=].) => @(print, 0, [N])' @(convert, [L], тысяч)

print := 2, [N=]_ => @(printH, [N], миллион)
print := 3, [N=]_ => @(printH, [N], миллиард)

то теперь, с учётом пунктов 1.2, 1.3 и 1.4, описание функции выглядит так:

print := (
    0, 0+$           =>
    0, 0?[DL=](..)$  => @(str10, [DL])
    0, (00)?[L]$     => @(str1, [L])
    0, [H]00$        => @(str100, [H])
    0, [H][DL=](..)$ => @(str100, [H])' @(str10, [DL])

    1, 0+$           =>
    1, [N=](.*1.)$   => @(print, 0, [N])' тысяч
    1, [N=].*1$      => @(print, 0, [N]0)'|одна' тысяча
    1, [N=].*2$      => @(print, 0, [N]0)'|две' тысячи
    1, [N=](.*[L=].) => @(print, 0, [N])' @(convert, [L], тысяч)

    2, [:N] => @(printH, [N], миллион)
    3, [:N] => @(printH, [N], миллиард)
)

1.5. Сокращённые имена для встроенных множеств.

<s>, <t>, <d> и <i> вместо <String>, <Text>, <Digit> и <Int> соответственно.

Раньше я думал, что длинные имена будут лучше и понятнее, но, поскольку DTL - это фактически расширение языка регулярных выражений, для него предельно важна краткость (иначе выражения будут неприлично длинными). А уж запомнить смысл отдельных множеств можно за пару дней работы с языком.

1.6. (!) Мемоизация.

С помощью инструкции [cache] вы указываете преобразователю, что для данного выражения следует запоминать получаемые результаты работы (кэшировать совпадения для каждой позиции во входной ленте).

С помощью мемоизации вы можете значительно снизить время преобразования для выражений, требующих большого числа переборов (что-то вроде <s>a<s>b<s>c<s> с входной строкой "bbbccbcaabccbcacbcbab").

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

1.7 (!) Глобальные инструкции и модули.

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

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

Для обращения к функциям и множествам из другого модуля сделан квалификатор по ключу. Подключая модуль a.dtl:

[module, a.dtl, my]

вы связываете его с ключом my, а затем можете обратиться к функции из него:

@(my\f, arg1, arg2, ...)

Если в главном файле функция f не определена, квалификатор можно опустить.

1.8 (!) Метапрограммирование.

Нужно сконструировать преобразователь на лету? Не вопрос. С помощью новой встроенной функции call теперь можно писать так:

@(call, "a => b", a)

Ваши возможности теперь безграничны.

2. Новое в механизме работы преобразователя.

2.1. (!) Ленивые вычисления.

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

Данное улучшение делает DTL серьёзным языком, позволяющим работать с данными большого объёма.

2.2. (!) Парадигма неизменяемых объектов.

Внутреннее улучшение. В декларативных языках часто бывает так, что объект невозможно изменить вообще; если вы хотите поменять какое-либо его свойство, вам нужно создать глубокую копию исходного объекта с изменённым свойством.

В DTL-преобразователе внутри теперь также используется большое число таких объектов. Гарантия неизменности позволяет сохранить только ссылку на нужный объект, не создавая себе его отдельную копию на случай его незапланированных сторонних изменений.

Также считаю очень существенным улучшением.

2.3. (!) Внешние функции.

Ну а вдруг вам не хватает возможностей DTL? Не вопрос. Теперь можно создать ссылку на функцию из .NET (которая пишется собственноручно и в которой вы можете открывать файлы, обращаться к БД и проч.), а в DTL она будет доступна под простым строковым именем. Наслаждайтесь.

3. Ну и, наконец, сама демонстрационная страница преобразователя стала выглядеть немного иначе и позволяет анализировать примеры программ более наглядно.

Также обновлён файл спецификации языка.

Текущая версия преобразователя как всегда доступна здесь:

http://vladimirkhil.com/lingware/dtl/implementation/

Загружено окт 17 2018, 6:41 pm GMT.