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

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

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

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

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

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

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

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

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

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

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

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

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

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

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#).

Тот же метод, предназначенный для вызова методов задания свойств, также применяется к любому методу возврата объектов (например, к методам фабрики):

type Widget() =
    member val Width = 1 with get,set
    member val Height = 1 with get,set

type WidgetFactory =
    static member MakeNewWidget() =
         new Widget()
    static member AdjustWidget(w: Widget) =
         w
let w = WidgetFactory.MakeNewWidget(Width=10)
w.Width // = 10
w.Height // = 1
WidgetFactory.AdjustWidget(w, Height=10)
w.Height // = 10

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

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

Можно указать необязательный параметр для метода, используя вопросительный знак перед именем параметра. Необязательные параметры интерпретируются как тип параметра F#, поэтому их можно запрашивать регулярно, используя match выражение с Some и None. Необязательные параметры разрешены только для элементов, а не функций, созданных с помощью 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

См. также