Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

Написанный таким образом, очевидно, что х передается только для того, чтобы быть "резьбовым" через последовательность функций. Прямая композиция выглядит намного приятнее:

let sumsqr = sqr >> sum

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

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

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