Поделиться через


Функции (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#).

Дополнительные сведения о параметрах см. в разделе Параметры и аргументы (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

Функция всегда возвращает только одно значение. Если функция не возвращает действительное значение, может возвращаться значение unit. Существует несколько способов возвращения нескольких элементов данных. Один из способов заключается в том, чтобы вернуть значение кортежа. Если функция возвращает значение кортежа, можно использовать в привязке let шаблон кортежа, чтобы назначить элементам кортежа несколько значений. Это проиллюстрировано в следующем коде.

let firstAndLast list1 =
    (List.head list1, List.head (List.rev list1))

let (first, last) = firstAndLast [ 1; 2; 3 ]
printfn "First: %d; Last: %d" first last

Выходные данные приведенного выше кода выглядят следующим образом.

  

Вторым способом возвращения нескольких фрагментов данных является использование ссылочных ячеек, а третьим способом — использование параметров byref. Дополнительные сведения и примеры см. в разделах Ссылочные ячейки (F#) и Параметры и аргументы (F#).

Вызов функции

Вызов функций осуществляется путем указания имени функции, пробела и следующих за ним аргументов через пробел. Например, чтобы вызвать функцию cylinderVolume и назначить результат значению vol, следует написать приведенный ниже код.

let vol = cylinderVolume 2.0 3.0

Если функция принимает кортеж как один параметр, вызов функции завершается списком в скобках, который выглядит как список аргументов в других языках. Пробел между именем функции и открывающей скобкой можно опустить. Например, ниже приводится функция cylinderVolume, параметром которой является кортеж.

let cylinderVolume(radius, length) =
    let pi = 3.14159
    length * pi * radius * radius

С кортежем в качестве аргумента вызов функции выглядит следующим образом.

let vol = cylinderVolume(2.0, 3.0)

Если функция не принимает параметров, укажите значение unit () в качестве аргумента, как в следующей строке кода.

initializeApp()

Имя функции само по себе является просто значением функции, поэтому если опустить скобки, обозначающие значение unit, функция просто является объектом ссылки, но не вызывается.

Частичное применение аргументов

Если предоставить меньшее число аргументов, чем задано, будет создана новая функция, ожидающая оставшиеся аргументы. Такой способ работы с аргументами называется каррированием и характерен для языков функционального программирования, таких как 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.

См. также

Другие ресурсы

Значения (F#)

Справочник по языку F#

Журнал изменений

Дата

Журнал

Причина

Май 2010

Исправлен пример кода в подразделе "Параметры".

Исправление ошибки содержимого.

Апрель 2011

Добавлено уточнение о параметрах и возвращаемых значениях.