Параметры и аргументы

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

Параметры и аргументы

Параметр термина используется для описания имен значений, которые должны быть предоставлены. Аргумент термина используется для значений, предоставленных для каждого параметра.

Параметры можно указать в кортеже или фигурной форме или в некоторой комбинации этих двух. Аргументы можно передать с помощью явного имени параметра. Параметры методов можно указать как необязательные и задать значение по умолчанию.

Шаблоны параметров

Параметры, предоставляемые функциям и методам, — это, как правило, шаблоны, разделенные пробелами. Это означает, что в принципе любой из шаблонов, описанных в выражениях match , можно использовать в списке параметров для функции или члена.

Методы обычно используют форму кортежа для передачи аргументов. Это обеспечивает более четкий результат с точки зрения других языков .NET, так как форма кортежа соответствует способу, который аргументы передаются в методах .NET.

Фигурная форма чаще всего используется с функциями, созданными с помощью let привязок.

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

// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...

Объединенные формы возможны, если некоторые аргументы находятся в кортежах, а некоторые — нет.

let function2 param1 (param2a, param2b) param3 = ...

Другие шаблоны также можно использовать в списках параметров, но если шаблон параметра не соответствует всем возможным входным данным, во время выполнения может быть неполное совпадение. Исключение MatchFailureException создается, если значение аргумента не соответствует шаблонам, указанным в списке параметров. Компилятор выдает предупреждение, если шаблон параметров допускает неполные совпадения. По крайней мере один другой шаблон обычно используется для списков параметров, и это шаблон с подстановочными знаками. Шаблон подстановочных знаков используется в списке параметров, если вы просто хотите игнорировать все предоставленные аргументы. Следующий код иллюстрирует использование шаблона с подстановочными знаками в списке аргументов.

let makeList _ = [ for i in 1 .. 100 -> i * i ]
// The arguments 100 and 200 are ignored.
let list1 = makeList 100
let list2 = makeList 200

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

[<EntryPoint>]
let main _ =
    printfn "Entry point!"
    0

Другие шаблоны, которые иногда используются в аргументах, являются as шаблоном и шаблонами идентификаторов, связанными с различающимися объединениями и активными шаблонами. Вы можете использовать шаблон объединения с одним регистром, как показано ниже.

type Slice = Slice of int * int * string

let GetSubstring1 (Slice(p0, p1, text)) =
    printfn "Data begins at %d and ends at %d in string %s" p0 p1 text
    text[p0..p1]

let substring = GetSubstring1 (Slice(0, 4, "Et tu, Brute?"))
printfn "Substring: %s" substring

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

Data begins at 0 and ends at 4 in string Et tu, Brute?
Et tu

Активные шаблоны могут быть полезны в качестве параметров, например при преобразовании аргумента в нужный формат, как показано в следующем примере:

type Point = { x : float; y : float }

let (| Polar |) { x = x; y = y} =
    ( sqrt (x*x + y*y), System.Math.Atan (y/ x) )

let radius (Polar(r, _)) = r
let angle (Polar(_, theta)) = theta

Шаблон можно использовать as для хранения совпадающего значения в виде локального значения, как показано в следующей строке кода.

let GetSubstring2 (Slice(p0, p1, text) as s) = s

Другой шаблон, который иногда используется, — это функция, которая оставляет последний аргумент без имени, предоставляя в качестве тела функции лямбда-выражение, которое немедленно выполняет сопоставление шаблона с неявным аргументом. Примером этого является следующая строка кода.

let isNil = function [] -> true | _::_ -> false

Этот код определяет функцию, которая принимает универсальный список и возвращает, true если список пуст и в false противном случае. Использование таких методов может усложнять чтение кода.

Иногда шаблоны, включающие неполные совпадения, полезны, например, если вы знаете, что списки в программе содержат только три элемента, можно использовать шаблон, как показано ниже в списке параметров.

let sum [a; b; c;] = a + b + c

Использование шаблонов с неполными совпадениями лучше всего зарезервировано для быстрого создания прототипов и других временных применений. Компилятор выдает предупреждение для такого кода. Такие шаблоны не могут охватывать общий случай всех возможных входных данных и, следовательно, не подходят для API компонентов.

Именованные аргументы

Аргументы для методов можно указать по позиции в списке аргументов, разделенных запятыми, или их можно передать методу явным образом, указав имя, за которым следует знак равенства и передаваемое значение. Если указано с помощью указания имени, они могут отображаться в другом порядке, отличном от используемого в объявлении.

Именованные аргументы могут сделать код более удобочитаемым и более адаптируемым к определенным типам изменений в API, таким как изменение порядка параметров метода.

Именованные аргументы разрешены только для методов, а не для letфункций с привязкой, значений функций или лямбда-выражений.

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

type SpeedingTicket() =
    member this.GetMPHOver(speed: int, limit: int) = speed - limit

let CalculateFine (ticket : SpeedingTicket) =
    let delta = ticket.GetMPHOver(limit = 55, speed = 70)
    if delta < 20 then 50.0 else 100.0

let ticket1 : SpeedingTicket = SpeedingTicket()
printfn "%f" (CalculateFine ticket1)

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

 type Account() =
    let mutable balance = 0.0
    let mutable number = 0
    let mutable firstName = ""
    let mutable lastName = ""
    member this.AccountNumber
       with get() = number
       and set(value) = number <- value
    member this.FirstName
       with get() = firstName
       and set(value) = firstName <- value
    member this.LastName
       with get() = lastName
       and set(value) = lastName <- value
    member this.Balance
       with get() = balance
       and set(value) = balance <- value
    member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
    member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount


let account1 = new Account(AccountNumber=8782108,
                           FirstName="Darren", LastName="Parker",
                           Balance=1543.33)

Дополнительные сведения см. в разделе Конструкторы (F#).

Необязательные параметры

Вы можете указать необязательный параметр для метода, используя вопросительный знак перед именем параметра. Необязательные параметры интерпретируются как тип параметра F#, поэтому их можно запрашивать обычным способом, с помощью match выражения и SomeNone. Необязательные параметры разрешены только для членов, а не для функций, созданных с помощью let привязок.

Существующие необязательные значения можно передать методу по имени параметра, например ?arg=None или ?arg=Some(3)?arg=arg. Это может быть полезно при создании метода, который передает необязательные аргументы другому методу.

Можно также использовать функцию defaultArg, которая задает значение по умолчанию необязательного аргумента. Функция defaultArg принимает необязательный параметр в качестве первого аргумента и значение по умолчанию в качестве второго.

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

type DuplexType =
    | Full
    | Half

type Connection(?rate0 : int, ?duplex0 : DuplexType, ?parity0 : bool) =
    let duplex = defaultArg duplex0 Full
    let parity = defaultArg parity0 false
    let mutable rate = match rate0 with
                        | Some rate1 -> rate1
                        | None -> match duplex with
                                  | Full -> 9600
                                  | Half -> 4800
    do printfn "Baud Rate: %d Duplex: %A Parity: %b" rate duplex parity

let conn1 = Connection(duplex0 = Full)
let conn2 = Connection(duplex0 = Half)
let conn3 = Connection(300, Half, true)
let conn4 = Connection(?duplex0 = None)
let conn5 = Connection(?duplex0 = Some(Full))

let optionalDuplexValue : option<DuplexType> = Some(Half)
let conn6 = Connection(?duplex0 = optionalDuplexValue)

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

Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false
Baud Rate: 300 Duplex: Half Parity: true
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false

Для C# и Visual Basic взаимодействия можно использовать атрибуты [<Optional; DefaultParameterValue<(...)>] в F#, чтобы вызывающие элементы отображали аргумент как необязательный. Это эквивалентно определению аргумента как необязательного в C# как в .MyMethod(int i = 3)

open System
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue("Hello world")>] message) =
        printfn $"{message}"

Можно также указать новый объект в качестве значения параметра по умолчанию. Например, элемент Foo может иметь необязательный CancellationToken атрибут в качестве входных данных:

open System.Threading
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue(CancellationToken())>] ct: CancellationToken) =
        printfn $"{ct}"

Значение, заданное в качестве аргумента, должно соответствовать DefaultParameterValue типу параметра. Например, следующее выражение недопустимо:

type C =
    static member Wrong([<Optional; DefaultParameterValue("string")>] i:int) = ()

В этом случае компилятор создает предупреждение и игнорирует оба атрибута. Обратите внимание, что значение null по умолчанию должно быть аннотировано типом, так как в противном случае компилятор выводит неправильный тип, т. е. [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Передача по ссылке

Передача значения F# по ссылке включает в себя ссылки byrefs, которые являются управляемыми типами указателей. Руководство по типу, который следует использовать, выглядит следующим образом:

  • Используйте, inref<'T> если вам нужно только прочитать указатель.
  • Используйте, outref<'T> только если необходимо записать указатель.
  • Используйте, byref<'T> если необходимо выполнить чтение и запись в указатель.
let example1 (x: inref<int>) = printfn $"It's %d{x}"

let example2 (x: outref<int>) = x <- x + 1

let example3 (x: byref<int>) =
    printfn $"It's %d{x}"
    x <- x + 1

let test () =
    // No need to make it mutable, since it's read-only
    let x = 1
    example1 &x

    // Needs to be mutable, since we write to it
    let mutable y = 2
    example2 &y
    example3 &y // Now 'y' is 3

Поскольку параметр является указателем, а значение является изменяемым, все изменения значения сохраняются после выполнения функции.

Кортеж можно использовать в качестве возвращаемого значения для хранения любых out параметров в методах библиотеки .NET. Кроме того, параметр можно рассматривать out как byref параметр. В следующем примере кода показаны оба способа.

// TryParse has a second parameter that is an out parameter
// of type System.DateTime.
let (b, dt) = System.DateTime.TryParse("12-20-04 12:21:00")

printfn "%b %A" b dt

// The same call, using an address of operator.
let mutable dt2 = System.DateTime.Now
let b2 = System.DateTime.TryParse("12-20-04 12:21:00", &dt2)

printfn "%b %A" b2 dt2

Массивы параметров

Иногда необходимо определить функцию, которая принимает произвольное количество параметров разнородного типа. Было бы нецелесообразно создавать все возможные перегруженные методы для учета всех типов, которые можно использовать. Реализации .NET обеспечивают поддержку таких методов с помощью функции массива параметров. Метод, принимаюющий массив параметров в сигнатуре, можно предоставить произвольное число параметров. Параметры помещаются в массив. Тип элементов массива определяет типы параметров, которые можно передать в функцию. Если массив параметров определен как System.Object тип элемента, клиентский код может передавать значения любого типа.

В F# массивы параметров можно определить только в методах. Их нельзя использовать в автономных функциях или функциях, определенных в модулях.

Массив параметров определяется с помощью атрибута ParamArray . Атрибут ParamArray можно применить только к последнему параметру.

Следующий код иллюстрирует вызов метода .NET, который принимает массив параметров и определение типа в F# с методом, принимаюющим массив параметров.

open System

type X() =
    member this.F([<ParamArray>] args: Object[]) =
        for arg in args do
            printfn "%A" arg

[<EntryPoint>]
let main _ =
    // call a .NET method that takes a parameter array, passing values of various types
    Console.WriteLine("a {0} {1} {2} {3} {4}", 1, 10.0, "Hello world", 1u, true)

    let xobj = new X()
    // call an F# method that takes a parameter array, passing values of various types
    xobj.F("a", 1, 10.0, "Hello world", 1u, true)
    0

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

a 1 10 Hello world 1 True
"a"
1
10.0
"Hello world"
1u
true

См. также раздел