Каковы преимущества и недостатки стиля "point free" в функциональном программировании?


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

3 63

3 ответа:

Я считаю, что цель состоит в том, чтобы быть кратким и выражать конвейерные вычисления как композицию функций, а не думать о резьбонарезной аргументы. Простой пример (в 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 передается в только быть "продетым нитку" через последовательность функций. Прямая композиция выглядит гораздо приятнее:

let sumsqr = sqr >> sum

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

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

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

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

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

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

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

так вот они. Главное преимущество point-free Программирование заключается в том, что они заставляют структурированный комбинаторный стиль, который делает эквационное рассуждение естественным. Эквационное рассуждение было особенно рекламировано сторонниками движения "Squiggol" (см. [1] [2]) и действительно использует справедливую долю беспунктовых комбинаторов и правил вычисления/перезаписи/рассуждения.

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

вы можете быть заинтересованы в Pointfree page в Хаскеле вики.

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

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

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

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

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

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

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