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

9

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

1) Для данного набора i / p, то же o / p возвращается независимо от времени вызова функции

2) не имеет побочных эффектов

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if(predicate(item)) result += item;
    return result;
}

Пример : Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});

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

rahulaga_dev
источник
1
Это не имеет никакого побочного эффекта - это имеет побочный эффект, когда itemпеременная мутирует в цикле.
Фабио
@ Фабио хорошо. Но можете ли вы уточнить масштабы побочных эффектов?
rahulaga_dev

Ответы:

16

Что в функциональном программировании имеет значение?

Функциональное программирование в принципе декларативно . Вы говорите, каков ваш результат, а не как его вычислить.

Давайте посмотрим на действительно функциональную реализацию вашего фрагмента. В Хаскеле это было бы:

predsum pred numbers = sum (filter pred numbers)

Понятно, каков результат? Именно такова сумма чисел, соответствующих предикату. Как это вычисляется? Мне все равно, спросите компилятор.

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

Решение «Функциональное программирование 101», которое не использует sum, с рекурсией:

sum pred list = 
    case list of
        [] -> 0
        h:t -> if pred h then h + sum pred t
                         else sum pred t

Все еще довольно ясно, каков результат с точки зрения одного вызова функции. Это либо 0, либо recursive call + h or 0, в зависимости от pred h. Все еще довольно просто, даже если конечный результат не сразу очевиден (хотя с небольшой практикой это действительно выглядит как forцикл).

Сравните это с вашей версией:

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if (predicate(item)) result += item;
    return result;
}

Что в итоге? О, я вижу: одно returnзаявление, никаких сюрпризов здесь return result.

А что есть result? int result = 0? Не кажется правильным. Вы делаете что-то позже с этим 0. Хорошо, вы добавляете items к нему. И так далее.

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

Итак, переменные и циклы не так?

Нет.

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

Переменные и циклы просто не являются функциональным программированием.

Резюме

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

Большинство распространенных языков позволяют использовать некоторые функциональные конструкции. Например, в Python вы можете выбрать между:

result = 0
for num in numbers:
    if pred(result):
        result += num
return result

или

return sum(filter(pred, numbers))

или

return sum(n for n in numbers if pred(n))

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

Frax
источник
спасибо за хорошее объяснение!
rahulaga_dev
1
@RahulAgarwal Этот ответ может показаться вам интересным, он прекрасно отражает тангенциальную концепцию установления истины и описания шагов. Мне также нравится фраза «декларативные языки содержат побочные эффекты, в то время как императивные языки этого не делают» - обычно функциональные программы имеют четкие и очень заметные разрывы между кодом состояния, работающим с внешним миром (или выполняющим некоторый оптимизированный алгоритм), и чисто функциональным кодом.
Frax
1
@Frax: спасибо !! Я буду смотреть в него. Также недавно натолкнулся на разговор Рича Хикки о значении ценностей. Это действительно блестяще. Я думаю, что одно правило большого пальца - «работай со значениями и выражением, а не работай с чем-то, имеющим значение и может измениться»
rahulaga_dev
1
@Frax: Также справедливо сказать, что FP - это абстракция над императивным программированием - потому что в конечном итоге кто-то должен научить машину «как это сделать», верно? Если да, то нет ли в императивном программировании более низкого уровня контроля по сравнению с FP?
rahulaga_dev
1
@Frax: Я бы согласился с Рахулом, что императив - это более низкий уровень в том смысле, что он ближе к базовой машине. Если бы аппаратное обеспечение могло копировать данные бесплатно, нам не потребовались бы деструктивные обновления для повышения эффективности. В этом смысле императивная парадигма ближе к металлу.
Джорджио
9

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

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

В вашем случае вы могли бы написать, numbers.Where(predicate).Sum()что явно намного проще. И проще означает меньше ошибок.

JacquesB
источник
Спасибо !! Я думаю, что мне не хватало поразительной черты - но парадигма функционального программирования применяется не только на уровне целых функций. Но теперь мне также интересно, как визуализировать эту границу. В основном с точки зрения потребителя, это чистая функция, но разработчик, который на самом деле написал эту функцию, не следовал рекомендациям по чистой функции? в замешательстве :(
rahulaga_dev
@RahulAgarwal: Какая граница?
JacquesB
Я запутался в смысле, если парадигма программирования квалифицируется как FP с точки зрения потребителя функций? Bcoz, если я смотрю на реализацию реализации Whereв numbers.Where(predicate).Sum()- он использует foreachцикл.
rahulaga_dev
3
@RahulAgarwal: Как потребитель функции, вам все равно, будет ли функция или модуль внутренне использовать изменяемое состояние, если она внешне чиста.
JacquesB
7

Хотя вы правы в том, что с точки зрения внешнего наблюдателя ваша Sumфункция чиста, внутренняя реализация явно не чиста - у вас есть сохраненное состояние, в resultкотором вы постоянно мутируете. Одна из причин, по которой следует избегать изменчивого состояния, заключается в том, что он создает большую когнитивную нагрузку на программиста, что, в свою очередь, приводит к большему количеству ошибок [необходима цитата] .

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

Филип Кендалл
источник