Истинно понимая разницу между процедурным и функциональным - PullRequest
109 голосов
/ 08 марта 2011

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

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

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

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

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

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

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

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

Ответы [ 8 ]

269 голосов
/ 12 марта 2011

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

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

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

В функциональном программировании мы можем объединить два значения функции, чтобы получить новое значение функции, используя такие операторы, как compose или лифт .Или мы можем объединить значение функции и значение данных для создания нового значения данных, используя такие операторы, как 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));
}

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

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

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

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

Дальнейшее чтение

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

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

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

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


Приложение - 2013

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

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

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

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

45 голосов
/ 08 марта 2011

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

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

12 голосов
/ 08 марта 2011
     Isn't that the same exact case for procedural programming?

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

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

11 голосов
/ 07 сентября 2014

Я не согласен с ответом 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));
}

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

  • Функциональное
  • Выражение ориентировано и
  • Итератор центрированный

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

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

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

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)

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

6 голосов
/ 08 марта 2011

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

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

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

2 голосов
/ 12 декабря 2013

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

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

2 голосов
/ 08 марта 2011

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

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

1 голос
/ 08 марта 2011

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

...