Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Функциональное программирование — это стиль программирования, который подчеркивает использование функций и неизменяемых данных. Типизированное функциональное программирование заключается в том, что функциональное программирование сочетается со статическими типами, например с F#. В целом в функциональном программировании выделяются следующие понятия:
- Функции в качестве основных конструкций, которые вы используете
- Выражения вместо инструкций
- Неизменяемые значения по переменным
- Декларативное программирование вместо императивного программирования
В рамках этой серии вы изучите концепции и шаблоны функционального программирования с помощью F#. По ходу дела вы также изучите F#.
Терминология
Функциональное программирование, как и другие парадигмы программирования, поставляется с словарем, который в конечном итоге потребуется научиться. Ниже приведены некоторые распространенные термины, которые вы увидите все время:
- Функция — это конструкция, которая создает выходные данные при указании входных данных. Более формально он сопоставляет элемент из одного набора с другим набором. Этот формализм переводится в конкретное разными способами, особенно при использовании функций, работающих с коллекциями данных. Это самая базовая (и важная) концепция функционального программирования.
- Выражение — выражение — это конструкция в коде, который создает значение. В F# это значение должно быть привязано или явно проигнорировано. Выражение может быть тривиально заменено вызовом функции.
- Чистота — чистота является свойством функции, так что ее возвращаемое значение всегда совпадает с теми же аргументами, и что его оценка не имеет побочных эффектов. Чистая функция полностью зависит от его аргументов.
- Референциальная прозрачность — ссылочная прозрачность — это свойство выражений, которое может быть заменено их выходными данными, не влияя на поведение программы.
- Неизменяемость — неизменяемость означает, что значение не может быть изменено на месте. Это отличается от переменных, которые могут измениться на месте.
Примеры
В следующих примерах показаны основные понятия.
Функции
Наиболее распространенная и фундаментальная конструкция в функциональном программировании — это функция. Ниже приведена простая функция, которая добавляет 1 в целое число:
let addOne x = x + 1
Его сигнатура типа выглядит следующим образом:
val addOne: x:int -> int
Подпись может быть прочитана как "addOne
принимает int
, именуемый x
, и будет производить int
". Более формально, addOne
— это мэппинг значения из множества целых чисел в множество целых чисел. Маркер ->
обозначает это сопоставление. В F# обычно можно просмотреть сигнатуру функции, чтобы получить представление о её функциях.
Поэтому почему сигнатура важна? В типизированном функциональном программировании реализация функции часто менее важна, чем фактическая подпись типа! Тот факт, что addOne
добавляет значение 1 к целому числу, интересен во время выполнения, но при создании программы определяет, как вы фактически будете использовать эту функцию, то, что она принимает и возвращает int
. Кроме того, после правильного использования этой функции (с учетом его сигнатуры типа), диагностика любых проблем может выполняться только в теле addOne
функции. Это импульс для типизированного функционального программирования.
Выражения
Выражения — это конструкции, которые оценивают значение. В отличие от операторов, которые выполняют действие, выражения можно рассматривать как действие, которое возвращает значение. Выражения почти всегда используются в функциональном программировании вместо инструкций.
Рассмотрим предыдущую функцию. addOne
Текст addOne
является выражением:
// 'x + 1' is an expression!
let addOne x = x + 1
Это результат этого выражения, определяющего тип addOne
результата функции. Например, выражение, составляющие эту функцию, может быть изменено на другой тип, например:string
let addOne x = x.ToString() + "1"
Теперь сигнатура функции:
val addOne: x:'a -> string
Так как любой тип в F# может вызывать ToString()
, тип x
был сделан универсальным (называется автоматическое обобщение), а результирующий тип — это string
.
Выражения — это не только функциональные тела. Вы можете иметь выражения, которые создают значение, используемое в другом месте. Один из распространенных:if
// Checks if 'x' is odd by using the mod operator
let isOdd x = x % 2 <> 0
let addOneIfOdd input =
let result =
if isOdd input then
input + 1
else
input
result
Выражение if
создает значение с именем result
. Обратите внимание, что вы можете полностью опустить result
, сделав if
выражение телом функции addOneIfOdd
. Ключевой вещью, которую следует помнить о выражениях, является то, что они создают значение.
Существует специальный тип, unit
который используется, когда нет ничего возвращаемого. Например, рассмотрим эту простую функцию:
let printString (str: string) =
printfn $"String is: {str}"
Подпись выглядит следующим образом:
val printString: str:string -> unit
Тип unit
указывает, что фактическое значение не возвращается. Это полезно, если у вас есть процедура, которая должна "выполнять работу", несмотря на отсутствие возвращаемого значения в результате этой работы.
Это отличается от императивного программирования, где эквивалентная if
конструкция является выражением, и создание значений часто выполняется через изменение переменных. Например, в C#код может быть написан следующим образом:
bool IsOdd(int x) => x % 2 != 0;
int AddOneIfOdd(int input)
{
var result = input;
if (IsOdd(input))
{
result = input + 1;
}
return result;
}
Следует отметить, что C# и другие языки C# поддерживают тернарное выражение, которое позволяет условному программированию на основе выражений.
В функциональном программировании редко происходит изменение значений с помощью инструкций. Хотя некоторые функциональные языки поддерживают инструкции и мутации, они не часто используются в функциональном программировании.
Чистые функции
Как упоминалось ранее, чистые функции — это функции, которые:
- Всегда вычисляйте одно и то же значение для одного и того же входного значения.
- Нет побочных эффектов.
В этом контексте полезно подумать о математических функциях. В математике функции зависят только от их аргументов и не имеют побочных эффектов. В математической функции f(x) = x + 1
значение f(x)
зависит только от значения x
. Чистые функции в функциональном программировании одинаковы.
При написании чистой функции функция должна зависеть только от его аргументов и не выполнять никаких действий, которые приводят к побочным эффектам.
Ниже приведен пример не чистой функции, так как она зависит от глобального, изменяемого состояния:
let mutable value = 1
let addOneToValue x = x + value
Функция addOneToValue
явно непрозрачна, так как value
может быть изменена в любое время, чтобы иметь другое значение, отличное от 1. Этот шаблон в зависимости от глобального значения следует избегать в функциональном программировании.
Ниже приведен еще один пример не чистой функции, так как он выполняет побочный эффект:
let addOneToValue x =
printfn $"x is %d{x}"
x + 1
Хотя эта функция не зависит от глобального значения, она записывает значение x
в выходные данные программы. Хотя при этом нет ничего неправильного, это означает, что функция не является чистой. Если другая часть программы зависит от чего-то внешнего в программе, например буфера вывода, вызов этой функции может повлиять на другую часть программы.
Удаление инструкции printfn
делает функцию чистой:
let addOneToValue x = x + 1
Хотя эта функция по сути не лучше предыдущей версии с printfn
оператором, она гарантирует, что всё, что делает эта функция, это возвращает значение. При вызове этой функции любое количество раз создается тот же результат: он просто создает значение. Прогнозируемость, обеспечиваемая чистотой, – это то, к чему стремятся многие функциональные программисты.
Неизменность
Наконец, одной из самых фундаментальных концепций типизированного функционального программирования является неизменяемость. В F#все значения по умолчанию неизменяемы. Это означает, что они не могут быть мутированы на месте, если вы явно не помечаете их как изменяемые.
На практике работа с неизменяемыми значениями означает, что вы изменяете подход к программированию с "Мне нужно изменить что-то", на "Мне нужно создать новое значение".
Например, добавление 1 в значение означает создание нового значения, не изменяя существующее:
let value = 1
let secondValue = value + 1
В F#следующий код не изменяет value
функцию. Вместо этого она выполняет проверку равенства:
let value = 1
value = value + 1 // Produces a 'bool' value!
Некоторые функциональные языки программирования вообще не поддерживают мутацию. В F# это поддерживается, но это не поведение по умолчанию для значений.
Эта концепция расширяется еще дальше к структурам данных. В функциональном программировании неизменяемые структуры данных, такие как наборы (и многое другое), имеют другую реализацию, чем вы изначально ожидали. Добавление элемента в множество концептуально не изменяет его, а создает новое множество с добавленным значением. Под поверхностью часто используется другая структура данных, которая позволяет эффективно отслеживать значение, чтобы в результате предоставить данные в соответствующем формате.
Этот стиль работы со значениями и структурами данных имеет решающее значение, так как позволяет обрабатывать любую операцию, которая изменяет что-то, как если бы она создает новую версию этой вещи. Это позволяет обеспечить согласованность таких аспектов, как равенство и сравнение в программах.
Дальнейшие шаги
В следующем разделе подробно рассматриваются функции, изучающие различные способы их использования в функциональном программировании.
Использование функций в F# подробно изучает функции, показывая, как их можно использовать в различных контекстах.
Дальнейшее чтение
Серия "Думать функционально" — это еще один отличный ресурс для изучения функционального программирования с помощью F#. В нем рассматриваются основы функционального программирования в прагматичном и удобном для чтения способе, используя функции F# для иллюстрации концепций.