Спокойный взгляд на одну из наиболее запутанных концепций функционального программирования

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

Некоторое время назад я наблюдал за докладом Скотт Влашин о функциональном программировании на конференции. Он сказал, что часто люди, не разбирающиеся в функциональном программировании, пугаются терминологии. Такие термины, как каррирование, частичное применение, функтор, монада и т. Д.

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

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

Сегодня давайте погрузимся в нашу тему, карри.

Параметры метода в OO Land

В большинстве языков программирования в стиле объектно-ориентированного программирования, таких как C #, Java, Python, C / C ++ и т. Д., Метод имеет заданное количество параметров.

Возьмите этот метод C # для объединения строк.

В качестве входных данных он принимает две строки и возвращает одну строку.

Каждый раз, когда вы вызываете этот метод, вы должны указать два параметра. Если вы этого не сделаете, компилятор C # пожалуется.

Функциональные параметры в функциональной стране

С другой стороны, в большинстве языков программирования функционального стиля, F #, Elm, Haskell и т. Д., Параметры функций определяются и используются по-разному.

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

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

Также обратите внимание, что определение находится в другом формате, “string -> string -> string”. Если вы никогда раньше не сталкивались с функциональным языком, это может быть немного странно.

Определение аналогичной функции в F # немного отличается.

Но подпись та же, вот скриншот этой функции в VS Code с использованием плагина Ionide.

Ionide автоматически добавляет определение функции (// string -> string -> string), вы заметите, что это то же самое, что и у Элма.

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

Подпись concateWordWith теперь “string -> string”.

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

Это почему?

Причина в каррировании.

Объяснение каррирования

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

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

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

Затем вы можете дать этой одной функции ввода один ввод, и она, наконец, вернет результат.

Итак, если вы видите функцию, имеющую сигнатуру “string -> string -> string”, объектно-ориентированный способ описания ее будет: «функция, которая принимает две строки в качестве входных и возвращает одну строку в качестве выходных».

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

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

Фактически, Википедия определяет каррирование как:

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

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

Почему карринг полезен?

Итак, теперь, когда вы знаете, что такое каррирование, следующий логичный вопрос: «А это полезно?» Неужели это всего лишь одна из тех «функциональных» вещей, которые существуют только для того, чтобы усложнять жизнь?

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

Рассмотрим следующий пример на C #, который добавляет 2 к каждому числу в списке.

Довольно простой код. Вот как это можно сделать на F #, не используя каррирование *.

Довольно просто. Но поскольку F # curries выполняет функции, вот более функциональный способ сделать это.

Функция add принимает два входа, но в этом примере мы даем ей один вход (2). Затем пусть List.map предоставит второй ввод, по одному для каждого элемента списка.

Конечно, это не меняет мир. В повседневной работе я пишу на C #, поэтому постоянно имею дело с функциями без каррирования. Но если вы проведете достаточно времени за языком карри, вы начнете упускать из виду маленькие ярлыки и тонкости.

* Да, каждая функция автоматически каррирована, так что технически это не верное утверждение. Но отличается от следующего примера.

Заключение

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

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

Источники