Logo

16 мая 2017 г.

Этюд 11. Замыкания. Что мешает ФП стать системным

Одной из объявленных целей разработки Кантора является популяризация функционального программирования и вынос его на системный уровень, чтобы в итоге родилось новое направление — функциональное системное программирование. Амбициозно, но факт.

Прежде чем браться за решение такой серьезной задачи, не мешало бы ответить на вопрос, что мешает функциональному программированию стать популярнее? Только ли груз совместимости, необходимость перестройки мышления и переобучения? Может быть, в самом ФП есть какие-то недостатки, являющиеся критическими с точки зрения массового производства ПО? Например, всё ли в порядке у него с технологичностью?

(Не)технологичность синтаксиса для API

Мне кажется, что важным фактором неприятия ФП в массовом производстве является нетехнологичность синтаксиса большинства функциональных языков с точки зрения простоты API, затрудняющего, в свою очередь, повторное использования кода. Особенно критично оно для системного программирования.

В промышленном программировании важны прототипы функций, являющиеся неотъемлемой частью интерфейсов библиотек, вместе с типами и констатами образующих модули — отлаженные куски кода для решения типовых задач. Для реальной разработки важно, что в случаях, когда модуль или библиотека не имеют документированного API, то глядя на ее интерфейс (заголовочный файл в Си), легко понять, какие функции в ней содержатся, какие параметры им нужно передавать при вызове, а также типы параметров. Это и есть технологичность. Популярные API служат явным или неявным стандартом решения задач, сокращают время на разработку за счет переиспользования кода.

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

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

Приведенный ниже пример с замыканием, возможно, не совсем релевантен API, поскольку замыкания — сугубо локальные сущности. Тем не менее, пример иллюстрирует  описанную выше проблему: сложность определения прототипа функции без полного прочтения ее текста.

Поскольку в этом этюде рассматриваются настоящие функциональные языки, в качестве спарринг-партнера Кантору выступит не привычный Delphi, а Scheme.

На Scheme

Пример замыкания на Scheme взят из Википедии, и на момент написания статьи имел следующий вид:
(define (make-adder n)       ; возвращает замкнутое лямбда-выражение
  (lambda (x)                ; в котором x - связанная переменная,
    (+ x n)))                ; а n - свободная (захваченная из внешнего контекста)

(define add1 (make-adder 1)) ; делаем процедуру для прибавления 1
(add1 10)                    ; возвращает 11

(define sub1 (make-adder -1)); делаем процедуру для вычитания 1
(sub1 10)                    ; возвращает 9
При помощи лямбда-выражения здесь описывается замыкание make-adder, а на его основе — функции add1 и sub1, затем вызываемые. Нетехнологичность здесь — в синтаксисе лямбда-выражений, текстуально оторванных от объявления самой функции. Я не знаком с видимостью имен и модульностью в Scheme, поэтому не могу сказать, какова область видимости объявленных в примере функций.

На Канторе

В Канторе отсутствуют лямбда-выражения по типу Scheme, поэтому замыкания описаны при помощи оператора частичного применения partial:
with
  make_adder[int n, x] = x + n;
  add1 partial make_adder[1];
  sub1 partial make_adder[-1];
return
  add1[10], sub1[10]; // возвращает (11, 9)
В Канторе замыкания — любые локальные функции, описанные без модификатора var.

В объявлении make_adder прототип указан полностью до знака равенства, так что при взгляде на функцию никакие скрытые параметры искать глазами не нужно. Замыкания add1 и sub1, объявленные через частичное применение, не содержат знака равенства и представляют собой интерфейсные объявления, аналогичные typedef в Си, только для функций. При этом наружу смотрит всего лишь одна анонимная функция, соответствующая оператору return, а замыкания ограничены локальной видимостью. Насколько это технологично и привычно для типичного программиста, решайте сами.

Чистота концепций или результат?

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

5 марта 2017 г.

Обрезка программ при помощи PE Tool

Когда Windows только освоила 32-битную платформу, все программы в ней грузились по одному виртуальному адресу. В Windows 2000 появилась возможность грузить программу по любому адресу, но он по-прежнему оставался фиксированным. А вот в ядре Windows Vista была реализована новая фишка — формирование адресного пространства программы случайным образом (ASLR), для реализации которой система научилась перемещать и программы тоже.

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

22 апреля 2016 г.

Этюд 10. Логические и битовые операции

На сайте compiler.su есть статья: «Так ли нужны операции «&&», «||» и «^^»?». Затронутая в ней тема очень интересна, особенно для функционального языка с выводом типов.

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

В других языках

В Delphi для логических и битовых операций используются одни и те же ключевые слова — not, and, or и xor, поэтому в выражениях нужно использовать скобки:
MaybeUTF8 := (FirstByte and $С0 = $80) and (NextByte and $С0 = $80);
В SQL проблемы нет, но в нем нет ни битовых операций, ни логического типа (зачастую). Например, в Oracle есть только встроенная функция bitand и нет логического типа:
select nvl((select 1 from dual where bitand(first_byte, 192) = 128 and
  bitand(next_byte, 192) = 128), 0) maybe_utf8 from dual;
Для получения требуемого результата пришлось использовать числа 0 и 1, играющие роль логических значений, и встроенную функцию nvl, чтобы превратить null в 0. В целом код не слишком очевиден, но из него видно, что логические и битовые операции в SQL различаются, как и в Си. Напоминаю, что именно SQL является примером для подражания Кантора.

В Канторе

Поскольку в данный момент принято, что битовые и логические операции в Канторе различны, наш пример записывается без скобок:
maybeUTF8 = firstByte & $С0 = $80 && nextByte & $С0 = $80;
В сравнении вместо = можно писать ==, а вместо символьного && — ключевое слово and, то есть можно и так:
maybeUTF8 = firstByte & $С0 == $80 and nextByte & $С0 == $80;
Навскидку не могу сказать, какая запись наглядней и чище.

Что поменяется, если мы вдруг воспользуемся перегрузкой функций по типу и захотим использовать одни и те же символы/слова для записи обоих видов операций? Чисто внешне поменяется вроде бы мало:
maybeUTF8 = firstByte and $С0 == $80 and nextByte and $С0 == $80;
А вот с точки зрения синтаксического разбора мы придем к ситуации в Delphi, поскольку у предложенной записи возможны несколько интерпретаций, в том числе и такая:
maybeUTF8 = (firstByte and $С0) == ($80 and nextByte) and ($С0 == $80);
Сразу понятно, зачем в Delphi нужны скобки. Можно попытаться решить задачу установкой приоритетов, но они всегда будут неочевидны для рядового программиста, что превратит код в ребусы, а программирование на Канторе — в составление и разгадывание ребусов... По части ребусов как-то не хочется отбирать пальму первенства у Си, перед Кантором стоит противоположная задача.

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

Нашим последним спасением может стать вывод типов, имеющийся в Канторе как и в любом другом функциональном языке. Если из-за множественной перегрузки вывод неоднозначен, можно использовать оператор-подсказку is:
maybeUTF8 = (firstByte and $С0 == $80) is Core:Boolean and
  (nextByte and $С0 == $80) is Core:Boolean;
Однако, увы, результат не соответствует ожиданиям. Поскольку оператор is имеет наивысший приоритет, для применения его к части выражения выводимое подвыражение надо заключать в скобки, что сводит на нет весь смысл, — со скобками и без is всё понятно. Стало быть, нововведение не состоялось: в борьбе за бесскобочный синтаксис битовые и логические операции должны быть различны.

Напоминаю, что естественная запись операций в Канторе является лишь синтаксическим сахаром к вызовам функций, а в объектном коде всё хранится «как в Лиспе»:
new from firstByte.BitAnd($С0).Equals($80).And(
  nextByte.BitAnd($С0).Equals($80)
) as maybeUTF8;

9 апреля 2016 г.

Форматирование строк

Форматирование строк в Канторе будет реализовано как предметно-ориентированный язык (DSL). Символ традиционный — знак процента:
s = {%Строка {%} из {%}.%}.AsString[{index, count}];
Если свойству AsString будет сопоставлена операция *, вызов можно записать и так:
s = {%Строка {%} из {%}.%} * {index, count};
Формат {%} подразумевает, что тип сопоставляемого выражения будет передаваться через RTTI, как это сделано в Delphi. Соответствующая реализация в CoreLite пока находится в разработке (отложена), но повлияла на Кантор.

На Delphi

Штатная функция SysUtils.Format:
s = Format('Строка %d из %d.', [Index, Count]);
Удивительно, но формируемые компилятором Delphi сведения о типе передаваемых параметров лишь ограниченно используются в стандартных функциях RTL/VCL — только для генерации исключений о несоответствии типа. В известных мне форматтерах строк Delphi нет символа, соответствующего понятию «тип из параметра».

Поскольку необходимая информация передается, соответствующая реализация возможна. Формат «тип из параметра» будет реализован в CoreLite и в дальнейшем будет использоваться в Канторе.

Цели

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

14 мая 2015 г.

Фрактальная модель: теория объектно-ориентированной ОС

Эта статья продолжает серию публикаций о фрактальной модели, лежащей в основе объектно-ориентированной ОС, реализация которой запланирована в рамках проекта. Предыдущие статьи серии давали определение объектно-ориентированной ОС и подчеркивали важность наследования в ООП.

Данная статья основана на материалах 2012 года, публиковавшихся на предыдущем сайте проекта и даже упомянутом в качестве литературы к одной из статей конференции «Объектные системы — 2013» (см. ссылки в конце статьи).

Сейчас текст адаптирован под текущую ситуацию в проекте и публикуется как предварительный материал к описанию фрактальной модели как ключевой концепции. Изначально планировалось разбить его на несколько частей, но поскольку тема не развивалась с 2013 года, решено опубликовать всё в одной статье, закрыв технический долг по теориям ООП.