Почему у функциональных программ есть корреляция между успехом компиляции и правильностью?


12

Я работаю в течение четырех лет в функциональном программировании, с тех пор как я впервые начал работать с LINQ. Недавно я написал некоторый чистый функциональный код на C # и из первых рук заметил, что я читал о функциональных программах - что после компиляции они становятся правильными.

Я пытался понять, почему это так, но мне это не удалось.

Одно из предположений состоит в том, что при применении принципов ОО у вас есть «уровень абстракции», которого нет в функциональных программах, и этот уровень абстракции позволяет сделать контракты между объектами правильными, а реализация - неправильной.

Задумывался ли кто-нибудь об этом и придумал ли абстрактную причину корреляции между успехом компиляции и корректностью программы в функциональном программировании?


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

1
@delnan Я бы не сказал, что Lisp - это функциональный язык программирования, хотя его можно использовать для написания кода функционального программирования. Clojure, который является диалектом Lisp, является функциональным языком программирования
sakisk

2
Я согласен с @delnan. Это утверждение больше относится к статически типизированным функциональным языкам программирования, особенно к Haskell, который использует систему Хиндли-Милнера. Я думаю, что основная идея заключается в том, что если вы правильно понимаете типы, то уверенность в правильности вашей программы возрастает.
Сакиск

1
Функциональный код может содержать столько же абстракций и косвенных ссылок, сколько и ваш типичный основной код ООП (если не больше). Дьявол кроется в деталях - меньше побочных эффектов и отсутствие нуля означает меньше невидимого состояния для отслеживания и меньше шансов испортить. Обратите внимание, что вы можете применять те же принципы в основных императивных языках, это просто больше работы и часто более многословно (например, нужно шлепать finalпо всему).
Довал

1
Не полный ответ, а ввод программы - грубая форма формальной проверки программы. В общем, объектно-ориентированные программы имеют либо сложные, либо очень простые системы типов, поскольку необходимо принимать во внимание подстановку - в большинстве случаев они делаются несостоятельными ради удобства. OTOH ML-подобные системы могут использовать CH в полной мере, так что вы можете кодировать доказательство только в виде типов и использовать компилятор в качестве средства проверки корректности.
Мацей Пехотка

Ответы:


12

Я могу написать этот ответ как кто-то, кто много доказывает, поэтому для меня правильность - это не только то, что работает, но то, что работает и легко доказать.

Во многих смыслах функциональное программирование является более строгим, чем императивное программирование. В конце концов, ничто не мешает вам никогда не мутировать переменную в C! На самом деле, большинство функций в языках FP просто говорить о нескольких основных функциях. Все это в значительной степени сводится к лямбдам, применению функций и сопоставлению с образцом!

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

Если вы начнете с безопасных ножниц вместо меча, бег будет умеренно менее опасным.

Теперь посмотрим на ваш вопрос: как все это вписывается в «это компилируется и работает!» явления. Я думаю, что большая часть этого является той же самой причиной, почему легко доказать код! В конце концов, когда вы пишете программное обеспечение, вы создаете неформальное доказательство его правильности. Из-за этого то, что покрыто вашими естественными доказательствами ручной обработки и собственным представлением компиляторов, является верным (проверка типов).

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


1
Мне нравится ваш ответ, но я не понимаю, как он отвечает на вопрос ОП
Сакиск

1
@faif Расширил мой ответ. TLDR: каждый математик.
Даниэль Гратцер

«Используя 101 различную хитрость для программы, мы должны рассуждать о вещах так, как будто любая из этих 101 вещи может произойти!»: Я где-то читал, что вам нужно быть гением, чтобы программировать с мутацией, потому что вы должны сохранять много информации в вашей голове.
Джорджио

12

основная абстрактная причина корреляции между успехом компиляции и корректностью программы в функциональном программировании?

Изменчивое состояние.

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

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

Если вы хорошо выполняете функциональное программирование, нет изменяемого состояния (или очень мало).

Здесь есть некоторые споры о причинности - если программы без состояния работают после компиляции чаще, потому что компилятор может отлавливать больше ошибок, или если программы без состояния работают после компиляции чаще, потому что этот стиль программирования производит меньше ошибок.

Скорее всего, это и то и другое в моем опыте.


2
«Вероятно, в моем опыте сочетается и то, и другое». У меня такой же опыт. Статическая типизация улавливает ошибки во время компиляции также при использовании императивного языка (например, Pascal). В FP предотвращение изменчивости и, я бы добавил, использование более декларативного стиля программирования облегчает анализ кода. Если язык предлагает оба варианта, вы получаете оба преимущества.
Джорджио

7

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

for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        iterator.remove();
    }
}

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

Это не очень сложно сделать таким универсальным, поэтому он будет работать не только с коллекциями Strings, но без первоклассных функций вы не можете заменить предикат (условие внутри if), поэтому этот код имеет тенденцию копироваться и вставляться и немного изменен.

Комбинируйте первоклассные функции, которые дают вам возможность передавать предикат в качестве параметра, с ограничением неизменности, которое делает его очень раздражающим, если вы этого не делаете, и вы придумываете простые строительные блоки, как filter, например , в этом коде Scala это делает то же самое:

list filter (!_.isEmpty)

Теперь подумайте о том, что система типов проверяет для вас во время компиляции в случае Scala, но эти проверки также выполняются динамическими системами типов при первом запуске:

  • listдолжен быть какой-то тип, который поддерживает filterметод, а именно коллекция.
  • Элементы listдолжны иметь isEmptyметод, который возвращает логическое значение.
  • Результатом будет (потенциально) меньшая коллекция с элементами того же типа.

После того, как эти вещи были проверены, какие другие способы остаются для программиста, чтобы испортить? Я случайно забыл !, что вызвало крайне очевидный сбой тестового случая. Это в значительной степени единственная доступная ошибка, которую я могу сделать, и я сделал это только потому, что непосредственно переводил код, проверенный на обратное условие.

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

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


«После того, как эти вещи были проверены, какие еще способы оставлены для программиста, чтобы испортить?»: Это как-то подтверждает мой опыт, что (1) статическая типизация + (2) функциональный стиль оставляют меньше способов испортить вещи. В результате я стремлюсь получить правильную программу быстрее и мне нужно меньше писать модульных тестов при использовании FP.
Джорджио

2

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

Как вы описываете, аспект языка программирования, который может как-то соотносить успешную компиляцию с корректностью типов во время выполнения, - это статическая типизация, и даже тогда, только если вы не ослабляете средство проверки типов с помощью приведений, которые могут быть утверждены только во время выполнения (в средах с строго типизированные значения или места, например, Java или .Net) или их нет вообще (в средах, где информация о типе теряется или при слабой типизации, например, C и C ++).

Тем не менее, функциональное программирование само по себе может помочь другими способами, такими как избегание общих данных и изменяемого состояния.

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


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

2

Пояснения для менеджеров:

Функциональная программа похожа на одну большую машину, где все соединено, трубки, кабели. [Машина]

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

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


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

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


1
Лучшая анология: функциональное программирование похоже на службу поддержки без клиентов - все замечательно (если вы не ставите под сомнение цель или эффективность).
Брендан

@Brendan car & factory не дает такой плохой аналогии. Он пытается объяснить, почему (небольшие) программы на функциональном языке работают с большей вероятностью и менее подвержены ошибкам, чем «фабричные». Но на помощь, скажем, ООП приходит, что фабрика может производить несколько вещей и больше. Ваше сравнение уместно; как часто можно услышать, как FP может распараллелить и оптимизировать, но в действительности (без каламбура) дает медленные результаты. Я все еще держусь за FP.
Joop Eggen

Функциональное программирование в масштабе работает довольно хорошо для en.wikipedia.org/wiki/Spherical_cow .
День

@ Я сам не боялся бы проблем с осуществимостью работ по крупномасштабному проекту FP. Даже люблю это. Обобщение имеет свои ограничения. Но так как это последнее утверждение тоже является обобщением ... (спасибо за сферическую корову)
Joop Eggen
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.