Параметры и аргументы
В этом разделе описывается поддержка языка для определения параметров и передачи аргументов в функции, методы и свойства. В ней содержатся сведения о том, как передавать по ссылке, а также как определять и использовать методы, которые могут принимать переменное число аргументов.
Параметры и аргументы
Параметр термина используется для описания имен значений, которые должны быть предоставлены. Аргумент термина используется для значений, предоставленных для каждого параметра.
Параметры можно указать в кортеже или куриной форме или в какой-либо комбинации двух. Аргументы можно передать с помощью явного имени параметра. Параметры методов можно указать как необязательные и задать значение по умолчанию.
Шаблоны параметров
Параметры, предоставляемые функциями и методами, — это шаблоны, разделенные пробелами. Это означает, что, в принципе, любой из шаблонов, описанных в выражениях сопоставления, можно использовать в списке параметров для функции или члена.
Методы обычно используют форму кортежа для передачи аргументов. Это обеспечивает более четкий результат с точки зрения других языков .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#).
Тот же метод, предназначенный для вызова методов задания свойств, также применяется к любому методу возврата объектов (например, к методам фабрики):
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