Функции (F#)
Функции — это основной элемент выполнения программы в любом языке программирования.Как и в других языках, функция в языке 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.
Встроенный описатель подсказывает компилятору, что функция невелика и код функции можно интегрировать в тело вызывающего объекта.
Область
На любом уровне области, отличной от области модуля, не будет ошибкой повторно использовать имя функции.Если имя используется повторно, то имя, объявленное позже, перекрывает имя, объявленное ранее.Однако в области верхнего уровня в модуле имена должны быть уникальными.Например, следующий код приведет к возникновению ошибки, если будет введен в области модуля, но внутри функции будет допустим.
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)
Функция создает кортеж из одного аргумента типа any.Поскольку тип не задан, функция может использоваться с типом аргумента any.Дополнительные сведения см. в разделе Автоматическое обобщение (F#).
Тело функции
Тело функции может содержать определения локальных переменных и функций.Область таких переменных и функций — в теле текущей функции, но не вне его.Если включена возможность облегченного синтаксиса, необходимо использовать отступ для обозначения того, что определение находится внутри тела функции, как показано в следующем примере.
let cylinderVolume radius length =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
Дополнительные сведения см. в разделах Рекомендации по форматированию кода (F#) и Подробный синтаксис (F#).
Возвращаемые значения
Компилятор использует результирующее значение в теле функции, чтобы определить возвращаемое значение и тип.Компилятор может вывести тип результирующего выражения из предшествующих выражений.В функции 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
Рекурсивные функции
Рекурсивные функции — функции, вызывающие самих себя.Они требуют, чтобы вслед за ключевым словом let было указано ключевое слово rec.Рекурсивную функцию можно вызвать из тела функции как любую другую.Следующая рекурсивная функция вычисляет элемент последовательности Фибоначчи с индексом 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#).
Композиция функций и конвейеризация
Функции в F# могут состоять из других функций.Композиция двух функций function1 и function2 является другой функцией, которая представляет собой применение функции function1 и последующее применение function2:
let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100
Результат равен 202.
Конвейеризация позволяет объединить вызовы функций в цепочку последовательных операций.Конвейеризация работает следующим образом.
let result = 100 |> function1 |> function2
Снова будет получен результат 202.
Операторы объединения занимают 2 функции и возвращает функцию. напротив, функции и операторы конвейера принимают аргументов и возвращают значение.В следующем примере кода показано различие между конвейер и операторов композиции, отображение различий в сигнатурах и потреблении функции.
// 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
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x
// Backward pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo
// Result is 5
let result3 = Pipeline1 2
// Result is 6
let result4 = Pipeline2 2
Перегружать функции
С помощью методов типа, но не перегруженной функции.Для получения дополнительной информации см. Методы (F#).