Каковы преимущества и недостатки стиля «без точек» в функциональном программировании? - PullRequest
65 голосов
/ 15 апреля 2011

Я знаю, что в некоторых языках (Haskell?) Мы стремимся достичь стиля без точек или никогда явно не ссылаться на аргументы функции по имени.Это очень сложная концепция для меня, но она может помочь мне понять, каковы преимущества (или, возможно, даже недостатки) этого стиля.Кто-нибудь может объяснить?

Ответы [ 3 ]

73 голосов
/ 16 апреля 2011

Бессмысленный стиль рассматривается некоторыми авторами как ultimate функциональный стиль программирования.Проще говоря, функция типа t1 -> t2 описывает преобразование одного элемента типа t1 в другой элемент типа t2.Идея состоит в том, что «точечные» функции (написанные с использованием явных переменных) подчеркивают элементов (когда вы пишете \x -> ... x ..., вы описываете, что происходит с элементом x), а «без точек»функции (выраженные без использования переменных) подчеркивают само преобразование как композицию более простых преобразований.Сторонники бессмысленного стиля утверждают, что преобразования действительно должны быть центральной концепцией, и что точечная нотация, хотя и проста в использовании, отвлекает нас от этого благородного идеала.

Бессмысленное функциональное программирование было доступно дляочень долгое время.Это было уже известно логикам, которые изучали комбинаторную логику со времени основательной работы Моисея Шенфинкеля в 1924 году, и послужило основой для первого исследования того, что станет выводом типа ML Робертом Фейсом и Haskell Curry в 1950-х годах.

Идея создания функций из выразительного набора базовых комбинаторов очень привлекательна и применяется в различных областях, таких как языки манипулирования массивами, полученные из APL или библиотеки комбинатора синтаксического анализа, такие как Haskell Parsec .Известный сторонник бессмысленного программирования - Джон Бэкус .В своей речи 1978 года «Может ли программирование быть освобождено от стиля фон Неймана?» Он писал:

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

Итак, вот они.Основным преимуществом бессмысленного программирования является то, что они создают структурированный стиль комбинатора, который делает эквациональные рассуждения естественными.Сторонники движения «Squiggol» (см. [1] [2]) особенно рекламировали рациональные рассуждения и действительно используют значительную долю бессмысленных комбинаторов и правил вычислений / переписывания / рассуждений.

Наконец, одной из причин популярности бессмысленного программирования среди хаскеллитов является его отношение к теории категорий . В теории категорий морфизмы (которые можно рассматривать как «преобразования между объектами») являются основным объектом изучения и вычислений. В то время как частичные результаты позволяют выполнять рассуждение в определенных категориях в точечном стиле, общий способ построения, проверки и управления стрелками по-прежнему остается бессмысленным стилем, и другие синтаксисы, такие как строковые диаграммы, также демонстрируют эту «точечную чистоту». Между людьми, отстаивающими методы «алгебры программирования», и пользователями категорий в программировании существуют довольно тесные связи (например, авторы банановой статьи [2] являются / были категоричными категориями).

Вас может заинтересовать страница Pointfree вики Haskell.

Недостаток стиля pointfree довольно очевиден: чтение может быть настоящей болью. Причина, по которой мы все еще любим использовать переменные, несмотря на многочисленные ужасы затенения, альфа-эквивалентности и т. Д., Заключается в том, что это нотация, которую так естественно читать и думать. Общая идея состоит в том, что сложная функция (на прозрачном ссылочном языке) похожа на сложную систему сантехники: входные данные - это параметры, они попадают в некоторые каналы, применяются к внутренним функциям, дублируются (\x -> (x,x)) или забываются (\x -> (), труба никуда не ведет) и т. Д. И нотация переменных приятно неявна для всего этого механизма: вы даете имя входу и имена на выходах (или вспомогательных вычислениях), но вам не нужно описывать все план сантехники, куда маленькие трубы пойдут, не будет помехой для больших и т. д. Количество сантехники внутри чего-то такого короткого, как \(f,x,y) -> ((x,y), f x y), удивительно. Вы можете следить за каждой переменной по отдельности или читать каждый промежуточный водопроводный узел, но вам никогда не придется видеть весь механизм вместе. Когда вы используете стиль без точек, все это явно, вы должны все записать и посмотреть на него потом, а иногда это просто безобразно.

PS: эта концепция слежения тесно связана с языками стекового программирования, которые, вероятно, являются наименее значимыми (едва используемыми) языками программирования. Я бы порекомендовал попробовать в них немного программировать, просто чтобы почувствовать это (как я бы рекомендовал логическое программирование). См. Коэффициент , Кошка или почтенный Далее .

56 голосов
/ 15 апреля 2011

Я считаю, что цель состоит в том, чтобы быть кратким и выражать конвейерные вычисления в виде композиции функций, а не думать о продвижении аргументов через.Простой пример (на F #) - приведен:

let sum = List.sum
let sqr = List.map (fun x -> x * x)

Используется как:

> sum [3;4;5]
12
> sqr [3;4;5]
[9;16;25]

Мы можем выразить функцию «сумма квадратов» как:

let sumsqr x = sum (sqr x)

И используйте как:

> sumsqr [3;4;5]
50

Или мы могли бы определить это, пропустив x через:

let sumsqr x = x |> sqr |> sum

Если вы написали это так, очевидно, что x передается только в 1019 * быть «пронизанным» через последовательность функций.Прямая композиция выглядит намного лучше:

let sumsqr = sqr >> sum

Это более лаконично, и это другой способ думать о том, что мы делаем;составление функций, а не воображение процесса прохождения аргументов.Мы не описываем, как работает sumsqr.Мы описываем, что это .

PS: Интересный способ получить представление о композиции - попробовать программирование на языке сцепления, таком как Forth, Joy, Factor и т. Д.можно рассматривать как не что иное, как композицию (Forth : sumsqr sqr sum ;), в которой пробел между словами является оператором композиции .

PPS: Возможно, другие могли бы прокомментировать различия в производительности.Мне кажется, что композиция может уменьшить давление ГХ, сделав его более очевидным для компилятора, что нет необходимости создавать промежуточные значения, как при конвейерной обработке;помогает сделать так называемую проблему «вырубки лесов» более понятной.

3 голосов
/ 18 октября 2017

Хотя меня привлекает концепция «без очков» и я использую ее для некоторых вещей, и согласен со всеми позитивами, сказанными ранее, я нашел эти вещи с ней как негативные (некоторые подробно описаны выше):

  1. Более короткое обозначение уменьшает избыточность;в сильно структурированной композиции (стиль ramda.js, или без точек в Haskell, или на любом языке конкатенации) чтение кода является более сложным, чем линейное сканирование с помощью связки const и использование подсветки символов, чтобы увидеть, какая привязка идетв какой другой расчет вниз по течению.Помимо дерева и линейной структуры, потеря описательных имен символов делает функцию интуитивно понятной.Конечно, и древовидная структура, и потеря именованных привязок также имеют много положительных моментов, например, функции будут чувствовать себя более общими - не привязанными к какой-либо области приложения через выбранные имена символов - и древовидная структура семантически присутствует дажеесли привязки выложены и могут быть поняты последовательно (lisp let / let * style).

  2. Бессмысленно проще всего, когда вы просто просматриваете или сочетаете ряд функций, как этотакже приводит к линейной структуре, за которой мы, люди, легко находимся.Тем не менее, передача некоторых промежуточных вычислений через нескольких получателей является утомительной.Существуют все виды упаковки в кортежи, линзирование и другие кропотливые механизмы, позволяющие просто сделать некоторые вычисления доступными, что в противном случае было бы просто многократным использованием некоторой привязки значений.Конечно, повторяющаяся часть может быть извлечена как отдельная функция, и в любом случае, возможно, это хорошая идея, но есть также аргументы для некоторых не коротких функций, и даже если она извлечена, ее аргументы должны быть каким-то образом пропущены через оба приложения,и тогда может возникнуть необходимость запоминания функции, чтобы фактически не повторять расчет.Один из них будет использовать converge, lens, memoize, useWidth и т. Д.

  3. Специфично для JavaScript: сложнее случайно отладить.С линейным потоком let привязок легко добавить точку останова где угодно.При использовании стиля без точек, даже если точка останова каким-либо образом добавлена, поток значений трудно читать, например.Вы не можете просто запросить или навести курсор на некоторую переменную в консоли разработчика.Кроме того, поскольку бессмысленное использование точек не является родным в JS, библиотечные функции ramda.js или аналогичных файлов могут немного затенить стек, особенно при обязательном каррировании.

  4. Хрупкость кода, особеннона нетривиальных системах размеров и в производстве.Если появляется новое требование, вступают в действие вышеупомянутые недостатки (например, сложнее прочитать код следующего сопровождающего, который может оказаться самостоятельно через несколько недель, а также сложнее отследить поток данных для проверки).Но самое главное, что даже что-то, казалось бы, небольшое и невинное новое требование может потребовать совершенно иной структуризации кода.Можно утверждать, что это хорошо, потому что это будет кристально чистое представление новой вещи, но переписывание больших массивов бессмысленного кода занимает очень много времени, и тогда мы не упомянули тестирование.Таким образом, кажется, что более слабое, менее структурированное, основанное на лексическом присваивании кодирование может быть более быстро изменено.Особенно, если кодирование является исследовательским, и в области человеческих данных со странными соглашениями (время и т. Д.), Которые редко могут быть получены точно на 100%, и всегда может быть предстоящий запрос для обработки чего-то более точного или более в соответствии с потребностямиклиент, какой бы метод ни привел к быстрому повороту, имеет большое значение.

...