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, а замыкания ограничены локальной видимостью. Насколько это технологично и привычно для типичного программиста, решайте сами.

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

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

Комментариев нет :

Отправить комментарий

Выбрав в выпадающем списке пункт «Имя/URL», можно оставить комментарий от своего имени без предварительной регистрации.