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

Обучение вождению

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

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

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

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

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

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

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

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

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

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

Когда вы впервые начинаете изучать второй язык, вы задаете такие вопросы, как: «Как мне создать модуль? Как искать в массиве? Каковы параметры функции подстроки? »

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

Ваш первый космический корабль

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

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

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

Физика не изменилась. Точно так же, как вы перемещаетесь в той же Вселенной.

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

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

Забудь все, что знаешь

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

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

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

Как и в машине, вы делали резервную копию, чтобы выбраться с подъездной дорожки. Но в космическом корабле обратного пути нет. Теперь вы можете подумать: «ЧТО? НЕТ ОБРАТНОГО ?! КАК, ЧЕРТ, Я ДОЛЖЕН ЕХАТЬ БЕЗ РЕВЕРСА ?! »

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

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

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

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

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

Самое главное, чтобы вы понимали.

Чистота

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

Чистые функции - это очень простые функции. Они работают только со своими входными параметрами.

Вот пример чистой функции на языке Javascript:

var z = 10;
function add(x, y) {
    return x + y;
}

Обратите внимание, что функция add НЕ затрагивает переменную z. Он не читает из z и не записывает в z. Он считывает только входные данные x и y и возвращает результат их сложения.

Это чистая функция. Если бы функция add имела доступ к z, она больше не была бы чистой.

Вот еще одна функция, которую следует учитывать:

function justTen() {
    return 10;
}

Если функция justTen является чистой, она может только возвращать константу. Почему?

Потому что мы не давали ему никаких данных. И поскольку, чтобы быть чистым, он не может получить доступ ни к чему, кроме своих собственных входов, единственное, что он может вернуть, - это константа.

Поскольку чистые функции, не принимающие параметров, не работают, они не очень полезны. Было бы лучше, если бы justTen было определено как константа.

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

Рассмотрим эту функцию:

function addNoReturn(x, y) {
    var z = x + y
}

Обратите внимание, что эта функция ничего не возвращает. Он добавляет x и y и помещает их в переменную z но не возвращает.

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

Все полезные чистые функции должны что-то возвращать.

Давайте снова рассмотрим первую функцию add:

function add(x, y) {
    return x + y;
}
console.log(add(1, 2)); // prints 3
console.log(add(1, 2)); // still prints 3
console.log(add(1, 2)); // WILL ALWAYS print 3

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

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

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

writeFile(fileName);
updateDatabaseTable(sqlCmd);
sendAjaxRequest(ajaxRequest);
openSocket(ipAddress);

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

Чистые функции не имеют никаких побочных эффектов.

В императивных языках программирования, таких как Javascript, Java и C #, побочные эффекты присутствуют везде. Это очень затрудняет отладку, поскольку переменную можно изменить где угодно в вашей программе. Итак, если у вас есть ошибка из-за того, что переменная изменяется на неправильное значение в неподходящее время, куда вы смотрите? Где угодно? Это не хорошо.

В этот момент вы, вероятно, думаете: «КАК, ЧЕРТ, Я ДЕЛАТЬ ЧТО-ТО С ТОЛЬКО ЧИСТЫМИ ФУНКЦИЯМИ ?!"

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

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

Неизменность

Вы помните, когда впервые увидели следующий фрагмент кода:

var x = 1;
x = x + 1;

И тот, кто учил вас, сказал вам забыть то, что вы узнали на уроке математики? В математике x никогда не может быть равно x + 1.

Но в императивном программировании это означает, что нужно взять текущее значение x, добавить к нему 1 и поместить этот результат обратно в x.

Что ж, в функциональном программировании x = x + 1 недопустимо. Итак, вы должны вспомнить то, что вы забыли по математике… В некотором роде.

В функциональном программировании нет переменных.

Сохраненные значения по-прежнему называются переменными из-за истории, но они являются константами, т.е. когда x принимает значение, это значение остается неизменным.

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

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

addOneToSum y z =
    let
        x = 1
    in
        x + y + z

Если вы не знакомы с синтаксисом ML-Style, позвольте мне объяснить. addOneToSum - это функция, которая принимает 2 параметра: y и z. .

Внутри блока let x привязан к значению 1 , т. е. он равен 1 на всю оставшуюся жизнь. Его жизнь заканчивается, когда функция завершается, или, точнее, когда вычисляется блок let.

Внутри блока in вычисление может включать значения, определенные в блоке let, а именно. x. Результат вычисления x + y + z возвращается или, точнее, 1 + y + z - возвращается, поскольку x = 1.

И снова я слышу, как вы спрашиваете: «КАК, ЧЕРТ, Я ДОЛЖЕН ДЕЛАТЬ ЧТО-ТО БЕЗ ПЕРЕМЕННЫХ ?!»

Давайте подумаем, когда мы хотим изменить переменные. На ум приходят два общих случая: многозначные изменения (например, изменение одного значения объекта или записи) и однозначные изменения (например, счетчики циклов).

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

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

Ах да, и благодаря отсутствию петель.

«ЧТО НЕТ ПЕРЕМЕННЫХ И ТЕПЕРЬ НЕТ ПЕТЛЕЙ ?! НЕНАВИЖУ ТЕБЯ!!!"

Подожди. Дело не в том, что мы не можем делать циклы (это не каламбур), просто нет конкретных конструкций цикла, таких как for, while , do, repeat и т. д.

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

Вот два способа создания циклов в Javascript:

// simple loop construct
var acc = 0;
for (var i = 1; i <= 10; ++i)
    acc += i;
console.log(acc); // prints 55
// without loop construct or variables (recursion)
function sumRange(start, end, acc) {
    if (start > end)
        return acc;
    return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); // prints 55

Обратите внимание, как рекурсия, функциональный подход, выполняет то же самое, что и цикл for, вызывая себя с началом new ( start + 1) и new аккумулятор (acc + start). Он не изменяет старые значения. Вместо этого он использует новые значения, рассчитанные на основе старых.

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

В Elm легче читать и, следовательно, понимать:

sumRange start end acc =
    if start > end then
        acc
    else
        sumRange (start + 1) end (acc + start) 

Вот как это работает:

sumRange 1 10 0 =      -- sumRange (1 + 1)  10 (0 + 1)
sumRange 2 10 1 =      -- sumRange (2 + 1)  10 (1 + 2)
sumRange 3 10 3 =      -- sumRange (3 + 1)  10 (3 + 3)
sumRange 4 10 6 =      -- sumRange (4 + 1)  10 (6 + 4)
sumRange 5 10 10 =     -- sumRange (5 + 1)  10 (10 + 5)
sumRange 6 10 15 =     -- sumRange (6 + 1)  10 (15 + 6)
sumRange 7 10 21 =     -- sumRange (7 + 1)  10 (21 + 7)
sumRange 8 10 28 =     -- sumRange (8 + 1)  10 (28 + 8)
sumRange 9 10 36 =     -- sumRange (9 + 1)  10 (36 + 9)
sumRange 10 10 45 =    -- sumRange (10 + 1) 10 (45 + 10)
sumRange 11 10 55 =    -- 11 > 10 => 55
55

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

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

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

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

Еще в середине 90-х я написал игровой движок для Creature Crunch, и самым большим источником ошибок были проблемы с многопоточностью. Хотел бы я тогда знать о неизменности. Но тогда меня больше беспокоила разница между 2х или 4х скоростными приводами CD-ROM в игровой производительности.

Неизменяемость создает более простой и безопасный код.

Мой мозг!!!!

А пока хватит.

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

Наверх Далее: Часть 2

Если вам это понравилось, нажмите ниже, чтобы другие люди увидели это здесь, на Medium.

Обновление около 2021 года: у меня есть книга, в которой вы узнаете все из этой серии и многое другое: Упрощенное функциональное программирование: пошаговое руководство.

Если вы хотите присоединиться к сообществу веб-разработчиков, которые учатся и помогают друг другу в разработке веб-приложений с использованием функционального программирования в Elm, посетите мою группу в Facebook, Learn Elm Programming https : //www.facebook.com/groups/learnelm/

Мой Twitter: @cscalfani