Функции

Функции являются основным элементом выполнения программы на любом языке программирования. Как и в других языках, функция 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, чтобы определить тип выражения h * pi * r * r как 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

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


/// 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 является другая функция, которая представляет применение функции function1 с последующим применением функции function2:

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

Перегрузка функций

Можно перегружать методы типа, но не функции. Дополнительные сведения см. в статье Методы.

См. также