2009-05-10 9 views
107

Я недавно изучал функциональное программирование (в частности, Haskell, но я также изучал руководства по Lisp и Erlang). В то время как я нашел концепции очень просвещенными, я до сих пор не вижу практической стороны концепции «без побочных эффектов». Каковы практические преимущества этого? Я пытаюсь мыслить в функциональном мышлении, но есть некоторые ситуации, которые кажутся слишком сложными без способности легко сохранять состояние (я не считаю «монады» Хаскелла «легкими»).Преимущества программирования без гражданства?

Стоит ли продолжать изучать Haskell (или другой чисто функциональный язык) в глубину? Является ли функциональное или неформатированное программирование более продуктивным, чем процедурным? Возможно ли, что я продолжу использовать Haskell или другой функциональный язык позже, или я должен изучить его только для понимания?

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

ответ

139

Прочитано Functional Programming in a Nutshell.

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

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

+0

Красиво сказано и в точку! –

+3

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

+0

@Ray: Я бы также добавил распределенное программирование! –

3

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

+0

Да, я определенно изучил это. Параллельная модель Эрланга в частности очень интригующая. Тем не менее, на данный момент я не очень забочусь о параллелизме, а также о производительности. Есть ли бонус производительности от программирования без состояния? –

+2

@musicfreak, нет бонуса за производительность. Но в качестве примечания, современные языки FP все еще позволяют вам использовать состояние, если оно вам действительно нужно. – Unknown

+0

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

3

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

Но эффективность - это не единственная проблема. Чистая функция легче тестировать и отлаживать, так как все, что влияет на нее, явно указано. А при программировании на функциональном языке становится привычным делать как можно меньше функций «грязными» (с I/O и т. Д.). Таким образом, разделение материалов с учетом состояния является хорошим способом разработки программ даже на не-функциональных языках.

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

+0

Эта первая часть - действительно интересный момент, о котором я раньше никогда не думал. Благодаря! –

+1

@musicfreak: прочитайте о memoizing. –

+0

Предположим, что в вашем коде есть 'sin (PI/3)', где PI является константой, компилятор может оценить эту функцию * во время компиляции * и вставить результат в сгенерированный код. – Artelius

36

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

Вы можете найти хороший учебник с большим количеством примеров в статье Джона Хьюза Why Functional Programming Matters (PDF).

Вы будете gobs более продуктивным, особенно если вы выберете функциональный язык, который также имеет алгебраические типы данных и сопоставление образцов (Caml, SML, Haskell).

+0

Не будет ли mixins также предоставлять повторно используемый код аналогичным образом с ООП? Не выступать за ООП, просто пытаясь понять вещи сам. – mikemaccana

17

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

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

Например, даже если F # статически типизирован (все типы разрешены во время компиляции), вывод типа определяет, какие типы у вас есть, поэтому вам не нужно это говорить. И если он не может понять это, он автоматически делает вашу функцию/класс/всеобще. Поэтому вам никогда не приходится писать какие-либо общие данные, все это автоматически. Я считаю, что это означает, что я больше трачу время на размышления о проблеме и меньше того, как ее реализовать. На самом деле, всякий раз, когда я возвращаюсь на C#, я нахожу, что мне очень не хватает такого вывода, вы никогда не понимаете, как отвлекать его, пока вам не нужно это делать больше.

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

let matchingFactors = 
    factors 
    |> Seq.filter (fun x -> largestPalindrome % x = 0) 
    |> Seq.map (fun x -> (x, largestPalindrome/x)) 

Я понимаю, что делает фильтр затем карту (это преобразование каждого элемента) в C# было бы довольно простым, но вы должны думать на более низком уровне. В частности, вам нужно написать сам цикл и иметь свой собственный явный оператор if и такие вещи. Начиная с обучения F #, я понял, что мне было проще кодировать функциональным способом, где, если вы хотите фильтровать, вы пишете «фильтр», и если вы хотите отобразить карту, вы пишете «карту», ​​вместо того чтобы внедрять каждая из деталей.

Мне также нравится оператор |>, который, я думаю, отделяет F # от ocaml и, возможно, других функциональных языков. Это оператор трубы, он позволяет вам «вывести» выход одного выражения во вход другого выражения. Это заставляет код следовать, как я думаю больше. Как и в фрагменте кода выше, это говорит: «Возьмите последовательность факторов, отфильтруйте их, а затем нарисуйте». Это очень высокий уровень мышления, который вы не получаете на императивном языке программирования, потому что вы так заняты написанием циклов и операторов if. Это единственное, что я пропускаю, когда я перехожу на другой язык.

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

Редактировать: Я видел в одном из комментариев, что вы попросили пример «состояния» на функциональном языке программирования.F # можно записать императивно, так вот прямой пример того, как вы можете иметь изменяемое состояние в F #:

let mutable x = 5 
for i in 1..10 do 
    x <- x + i 
+1

Я согласен с вашим сообщением вообще, но |> не имеет ничего общего с функциональным программированием как таковым. На самом деле, 'a |> b (p1, p2)' является просто синтаксическим сахаром для 'b (a, p1, p2)'. Соедините это с правой ассоциативностью, и у вас это есть. –

+2

Правда, я должен признать, что, вероятно, много моего положительного опыта с F # больше связано с F #, чем с функциональным программированием. Но тем не менее, существует сильная корреляция между ними, и даже если такие вещи, как вывод типа и |>, не являются функциональным программированием как таковым, я бы сказал, что они «идут с территорией». По крайней мере, в общем. –

+0

|> - это просто еще одна функция инфикса более высокого порядка, в этом случае оператор функции-приложения. Определение собственных операторов верхнего уровня, инфикс * определенно * является частью функционального программирования (если вы не Schemer). Haskell имеет свой $, который является тем же самым, за исключением информации в потоках конвейера справа налево. –

11

Рассмотрим все трудные ошибки вы потратили много времени отладки.

Теперь, сколько из этих ошибок было вызвано «непреднамеренными взаимодействиями» между двумя отдельными компонентами программы? (Почти все ошибки с чередованием имеют такую ​​форму: расы, связанные с записью общих данных, взаимоблокировок ... Кроме того, обычно обнаруживаются библиотеки, которые оказывают неожиданное влияние на глобальное состояние или читают/записывают реестр/среду и т. Д.) I полагает, что по крайней мере 1 из 3 'жестких ошибок' попадают в эту категорию.

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

Это большая победа от «неизменности» ИМО. В идеальном мире мы все проектируем потрясающие API, и даже когда вещи будут изменчивыми, эффекты будут локальными и хорошо документированными, а «неожиданные» взаимодействия будут сведены к минимуму. В реальном мире существует множество API-интерфейсов, которые взаимодействуют с глобальным состоянием множеством способов, и они являются источником самых пагубных ошибок. Стремление к безгражданству стремится избавиться от непреднамеренных/неявных/закулисных взаимодействий между компонентами.

+5

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

2

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

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

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

Лучший подход заключается в том, чтобы хранить сеанс за веб-серверами в каком-то хранилище данных, в эти дни для этого доступны множество отличных продуктов nosql (redis, mongo, elasticsearch, memcached). Таким образом, веб-серверы не имеют состояния, но у вас все еще есть государственная серверная часть, и доступность этого состояния может управляться путем выбора правильной настройки хранилища данных. Эти хранилища данных обычно имеют большую избыточность, поэтому почти всегда возможно вносить изменения в ваше веб-приложение и даже в хранилище данных, не затрагивая пользователей.