Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Функциональное программирование — это стиль программирования, который подчеркивает использование функций и неизменяемых данных. Типизированное функциональное программирование заключается в том, что функциональное программирование сочетается со статическими типами, например с 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# для иллюстрации концепций.