Поистине понимая разницу между процедурной и функциональной

Мне действительно трудно понять разницу между процедурной и функциональной парадигмами программирования.

Вот первые два абзаца из статьи Википедии о функциональном программировании:

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

На практике разница между математическая функция и понятие "функции", используемой в императивном программирование является обязательным функции могут иметь побочные эффекты, изменение значения состояния программы. Из-за этого им не хватает ссылочных прозрачность, то есть тот же язык выражение может привести к значения в разное время в зависимости от состояние исполняющей программы. И наоборот, в функциональном коде выходное значение функции зависит от только на входные аргументы к функции, поэтому вызов функции f дважды с тем же значением для аргумент x приведет к тому же результат f(x) в оба раза. уничтожение побочные эффекты могут сделать это намного проще понимать и прогнозировать поведение программы, которая является одним из ключевых мотивации для разработки функциональное программирование.

В пункте 2, где говорится

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

Разве это не тот самый точный случай для процедурного программирования?

Что нужно искать в процедурных и функциональных, которые выделяются?

Ответ 1

Функциональное программирование

Функциональное программирование относится к способности рассматривать функции как значения.

Рассмотрим аналогию с "регулярными" значениями. Мы можем взять два целочисленных значения и объединить их с помощью оператора + для получения нового целого числа. Или мы можем умножить целое число на число с плавающей запятой, чтобы получить число с плавающей запятой.

В функциональном программировании мы можем комбинировать два значения функций для создания нового значения функции с помощью таких операторов, как compose или lift. Или мы можем комбинировать значение функции и значение данных для создания нового значения данных с помощью таких операторов, как map или fold.

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

Процедурное программирование

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

Контраст

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

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

Я буду считать, что этот пример понятен. Теперь функциональный стиль:

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

Работая изнутри, это определение делает следующие вещи:

  • compose(odd, length) объединяет функции odd и length для создания новой функции, которая определяет, является ли длина строки нечетной.
  • map(..., words) вызывает эту новую функцию для каждого элемента в words, в конечном итоге возвращает новый список логических значений, каждый из которых указывает, имеет ли соответствующее слово нечетное число символов.
  • apply(and, ...) применяет оператор "и" к результирующему списку и объединяет все логические элементы, чтобы получить конечный результат.

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

В этом примере также показаны типичные относительные размеры процедурного и функционального кода. Кроме того, он демонстрирует, что характеристики производительности процедурного кода могут быть легче увидеть, чем функциональный код. Рассмотрим: выполняют ли функции вычисления всех слов в списке или каждый раз останавливается сразу после нахождения первого слова длины длины? С другой стороны, функциональный код позволяет высококачественной реализации выполнять некоторую довольно серьезную оптимизацию, поскольку в ней прежде всего выражается намерение, а не явный алгоритм.

Дополнительная литература

Этот вопрос возникает много... см., например:

Премия лекции Джона Бэкуса "Тьюринг" подробно описывает мотивацию для функционального программирования:

Может ли программирование освободиться от стиля фон Неймана?

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


Addendum - 2013

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

  • запрос (например, понимание списков, интегрированный язык)
  • поток данных (например, неявная итерация, объемные операции)
  • объектно-ориентированные (например, инкапсулированные данные и методы)
  • ориентированный на язык (например, специфичный для приложения синтаксис, макросы)

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

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

Ответ 2

Реальная разница между функциональным и императивным программированием - это мышление - императивные программисты думают о переменных и блоках памяти, тогда как программисты-программисты думают: "Как я могу преобразовать мои входные данные в свой вывод data" - ваша "программа" - это конвейер и набор преобразований для данных, чтобы взять их из входа в вывод. Что интересная часть IMO, а не бит "Не используйте переменные".

Как следствие этого мышления, программы FP обычно описывают, что произойдет, а не конкретный механизм того, как это произойдет - это мощно, потому что, если мы сможем четко указать, что "Выбрать" и "Где" и "Агрегатировать", означает, что мы можем поменять свои реализации, как и в случае с AsParallel(), и внезапно наше однопоточное приложение масштабируется до n ядер.

Ответ 3

     Isn't that the same exact case for procedural programming?

Нет, потому что процедурный код может иметь побочные эффекты. Например, он может сохранять состояние между вызовами.

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

Ответ 4

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

Сначала его код:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

и

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

Первое, что нужно отметить, это то, что он объединяется:

  • Functional
  • Ориентированное выражение и
  • Итератор центрический

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

Давайте быстро поговорим об этом.

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

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and

Это почти то же самое, что указано ранее, за исключением того, что функции связаны цепями только через цепочки операторов и привязок.

Стиль программирования, ориентированный на итератор, может быть выполнен с помощью Python. Пусть используется чисто итеративный стиль, ориентированный на итератор:

def all_odd(words):
    lengths = (len(word) for word in words)
    each_odd = (odd(length) for length in lengths)
    return all(each_odd)

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

Конечно, вы можете сжать это:

def all_odd(words):
    return all(odd(len(word)) for word in words)

Императив не выглядит так плохо сейчас, а?:)

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

function allOdd(words) {
    for (var i = 0; i < length(words); ++i) {
        if (!odd(length(words[i]))) {
            return false;
        }
    }
    return true;
}

Используя итераторы, вы можете:

function allOdd(words) {
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
}

Итак, какова точка функционального языка, если разница между ними:

return all(odd(len(word)) for word in words)
return apply(and, map(compose(odd, length), words))
for (word : words) { if (!odd(length(word))) { return false; } }
return true;


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

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

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

all = partial(apply, and)

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

Ответ 5

В процедурной парадигме (вместо этого я должен сказать "структурированное программирование"?), у вас есть разделяемая память и инструкции, которые читают/записывают ее в некоторой последовательности (одна за другой).

В функциональной парадигме у вас есть переменные и функции (в математическом смысле: переменные не меняются со временем, функции могут вычислять только что-то на основе их входов).

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

Ответ 6

Очаровательный Python: функциональное программирование на Python из IBM Developerworks действительно помог мне понять разницу.

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

Ответ 7

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

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

Ответ 8

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