Функции
Функции являются основным элементом выполнения программы на любом языке программирования. Как и в других языках, функция F# имеет имя, может иметь параметры и принимать аргументы, а также имеет тело. F# также поддерживает конструкции функционального программирования, например, обработку функций как значений, использование неименованных функций в выражениях, объединение функций для образования новых функций, каррированные функции и неявное определение функций посредством частичного применения аргументов функции.
Функции определяются с помощью ключевого слова let
, или, если функция является рекурсивной, сочетания ключевых слов let rec
.
Синтаксис
// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body
Примечания
Элемент function-name — это идентификатор, который представляет функцию. Элемент parameter-list состоит из последовательных параметров, разделенных пробелами. Вы можете указать явный тип для каждого параметра, как описано в разделе "Параметры". Если не указать конкретный тип аргумента, компилятор пытается определить тип из тела функции. Элемент function-body состоит из выражения. Выражение, представляющее тело функции, обычно является составным. Оно состоит из нескольких выражений, которые в результате дают итоговое выражение, являющееся возвращаемым значением. Элемент return-type представляет собой двоеточие, за которым следует тип, и является необязательным. Если вы явно не указываете тип возвращаемого значения, компилятор определяет тип возвращаемого значения из итогового выражения.
Простое определение функции выглядит следующим образом:
let f x = x + 1
В предыдущем примере f
является именем функции, x
— аргументом, имеющим тип int
, x + 1
является телом функции, а возвращаемое значение имеет тип int
.
Функции могут быть помечены как inline
. Сведения о inline
см. в статье Встраиваемые функции.
Область
На любом уровне области, отличной от области модуля, не будет ошибкой повторно использовать значение или имя функции. При повторном использовании имя, объявленное позже, затемняет имя, объявленное раньше. Но в области верхнего уровня в модуле имена должны быть уникальными. Например, следующий код вызывает ошибку, когда отображается в области модуля, и не вызывает ее при отображении внутри функции:
let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []
let function1 () =
let list1 = [1; 2; 3]
let list1 = []
list1
Но следующий код допустим на любом уровне области:
let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
let list1 = [1; 5; 10]
x + List.sum list1
Параметры
Имена параметров указываются после имени функции. Можно указать тип для параметра, как показано в следующем примере:
let f (x : int) = x + 1
Если тип указан, он стоит после имени параметра и отделяется от него двоеточием. Если тип параметра не указан, он определяется компилятором. Например, в следующем определении функции аргумент x
определяется, как относящийся к типу int
, так как 1 имеет тип int
.
let f x = x + 1
Но компилятор попытается сделать функцию как можно более универсальной. Например, изучите следующий код:
let f x = (x, x)
Функция создает кортеж из одного аргумента любого типа. Так как тип не задан, функция может использоваться с любым типом аргумента. Дополнительные сведения см. в статье Автоматическое обобщение.
Тела функций
Тело функции может содержать определения локальных переменных и функций. Такие переменные и функции находятся в области действия внутри тела текущей функции, но не за его пределами. Чтобы указать, что определение находится в теле функции, необходимо использовать отступ, как показано в следующем примере:
let cylinderVolume radius length =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
Дополнительные сведения см. в статьях Рекомендации по форматированию кода и Подробный синтаксис.
Возвращаемые значения
Компилятор использует итоговое выражение в теле функции, чтобы определить возвращаемое значение и его тип. Компилятор может вывести тип итогового выражения из предыдущих выражений. В функции cylinderVolume
, показанной в предыдущем разделе, тип pi
определяется по типу литерала 3.14159
как float
. Компилятор использует тип pi
, чтобы определить тип выражения length * pi * radius * radius
как float
. Таким образом, общим типом возвращаемого значения функции является float
.
Чтобы явно указать тип возвращаемого значения, напишите код следующим образом:
let cylinderVolume radius length : float =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
В приведенном выше коде компилятор применяет float ко всей функции. Если требуется применить его и к типам параметров, используйте следующий код:
let cylinderVolume (radius : float) (length : float) : float
Вызов функции
Вы можете вызывать функции, указав имя функции, за которыми следует пробел, а затем аргументы, разделенные пробелами. Например, чтобы вызвать функцию cylinderVolume и назначить результат значению vol, составьте следующий код:
let vol = cylinderVolume 2.0 3.0
Частичное применение аргументов
Если число предоставленных аргументов меньше заданного их количества, создается новая функция, ожидающая оставшиеся аргументы. Такой метод работы с аргументами называется каррингом и характерен для языков функционального программирования, таких как F#. Предположим, вы работаете с двумя размерами труб: одна имеет радиус 2,0, а другая — радиус 3,0. Можно создать функции, определяющие объем трубы следующим образом:
let smallPipeRadius = 2.0
let bigPipeRadius = 3.0
// These define functions that take the length as a remaining
// argument:
let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius
Затем необходимо указать окончательный аргумент для различных длин канала двух разных размеров:
let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2
Рекурсивные функции
Рекурсивными называются функциями, которые вызывают сами себя. Для них нужно указать ключевое слово rec, следующее за ключевым словом let. Вызов рекурсивной функции в теле функции выполняется так же, как и для любой другой функции. Следующая рекурсивная функция вычисляет n-ое число Фибоначчи. Последовательность чисел Фибоначчи известна с античных времен. В ней каждый последующий элемент равен сумме двух предыдущих чисел.
let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)
Некоторые рекурсивные функции могут переполнять стек программы или выполнять неэффективно, если вы не пишете их с осторожностью и с пониманием специальных методов, таких как использование рекурсии хвоста, накопительных и продолжений.
Значения функции
В языке F# все функции считаются значениями. На самом деле они называются значениями функции. Так как функции являются значениями, они могут использоваться в качестве аргументов для других функций или в других контекстах, где применяются значения. Ниже приведен пример функции, которая принимает значение функции в качестве аргумента:
let apply1 (transform : int -> int ) y = transform y
Укажите тип значения функции с помощью токена ->
. В левой части токена находится тип аргумента, а в правой части — возвращаемое значение. В предыдущем примере apply1
является функцией, которая принимает функцию transform
как аргумент, где transform
— это функция, которая принимает целое число и возвращает другое целое число. В следующем коде показано использование apply1
:
let increment x = x + 1
let result1 = apply1 increment 100
После выполнения предыдущего кода result
будет иметь значение 101.
Несколько аргументов разделяются токенами ->
, как показано в следующем примере:
let apply2 ( f: int -> int -> int) x y = f x y
let mul x y = x * y
let result2 = apply2 mul 10 20
Результат равен 200.
Лямбда-выражения
Лямбда-выражение — это неименованная функция. В предыдущих примерах вместо определения именованных функций increment и mul можно было воспользоваться лямбда-выражением:
let result3 = apply1 (fun x -> x + 1) 100
let result4 = apply2 (fun x y -> x * y ) 10 20
Лямбда-выражения определяются с помощью ключевого слова fun
. Лямбда-выражение напоминает определение функции, за исключением того, что вместо токена =
для отделения списка аргументов от тела функции используется токен ->
. Как и в обычном определении функции, типы аргументов могут выводиться или указываться явно, а тип возвращаемого значения лямбда-выражения выводится из типа последнего выражения в теле. Дополнительные сведения см. в разделе Лямбда-выражения: ключевое слово fun
.
Конвейеры
Оператор конвейера |>
широко используется при обработке данных в F#. Этот оператор позволяет гибко устанавливать "конвейеры" функций. Конвейеризация позволяет объединять вызовы функций в виде последовательных операций.
let result = 100 |> function1 |> function2
В следующем примере показано, как можно использовать эти операторы для создания простого функционального конвейера.
/// Square the odd values of the input and add one, using F# pipe operators.
let squareAndAddOdd values =
values
|> List.filter (fun x -> x % 2 <> 0)
|> List.map (fun x -> x * x + 1)
let numbers = [ 1; 2; 3; 4; 5 ]
let result = squareAndAddOdd numbers
Результат [2; 10; 26]
. В предыдущем примере показаны функции обработки списков, демонстрирующие, как можно использовать функции для обработки данных при создании конвейеров. Сам оператор конвейера определяется в основной библиотеке F# следующим образом:
let (|>) x f = f x
Композиция функций
В F# функции могут состоять из других функций. Состав двух функций function1 и function2 — это другая функция, представляющая приложение функции 1 , за которой следует приложение функции2:
let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100
Результат равен 202.
Оператор >>
композиции принимает две функции и возвращает функцию; напротив, оператор |>
конвейера принимает значение и функцию и возвращает значение. В следующем примере кода показано различие между операторами конвейера и композиции. Там продемонстрировано отличие в сигнатурах и использовании функций.
// Function composition and pipeline operators compared.
let addOne x = x + 1
let timesTwo x = 2 * x
// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo
// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo
// Result is 5
let result1 = Compose1 2
// Result is 6
let result2 = Compose2 2
// Pipelining
// Pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo
// Backward pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x
// Result is 5
let result3 = Pipeline1 2
// Result is 6
let result4 = Pipeline2 2
Перегрузка функций
Можно перегружать методы типа, но не функции. Дополнительные сведения см. в статье Методы.