В настоящее время мы делаем это...
let parseDate defaultVal text =
match DateTime.TryParse s with
| true, d -> d
| _ -> defaultVal
Можно ли сделать это...
let d : DateTime = tryParse DateTime.MinValue "2015.05.01"
В настоящее время мы делаем это...
let parseDate defaultVal text =
match DateTime.TryParse s with
| true, d -> d
| _ -> defaultVal
Можно ли сделать это...
let d : DateTime = tryParse DateTime.MinValue "2015.05.01"
да. Добро пожаловать в мир ограничений элементов, значений ref и byref.
let inline tryParseWithDefault
defaultVal
text
: ^a when ^a : (static member TryParse : string * ^a byref -> bool)
=
let r = ref defaultVal
if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r.contents))
then !r
else defaultVal
defaultVal
и text
являются формальными параметрами и будут выведены. Здесь text
уже ограничено значением string
, поскольку оно используется в качестве первого параметра в вызове статического метода SomeType.TryParse
, как будет объяснено позже. defaultVal
ограничено тем, чем является ^a
, поскольку это возможное значение результата для выражения if..then..else
.^a
– это статически разрешенный параметр типа (по сравнению с параметром универсального типа в форме 'a
). В частности, ^a
будет преобразован во время компиляции в определенный тип. Следовательно, функция, в которой она размещена, должна быть помечена inline
, что означает, что каждый вызов функции станет заменой на месте при этом вызове этим телом функции, при этом каждый параметр статического типа станет определенным типом; в этом случае, каким бы ни был тип defaultVal
. Не существует ограничений базового типа или типа интерфейса, ограничивающих возможный тип defaultVal
. Однако вы можете указать статические и экземплярные ограничения члена, как это сделано здесь. В частности, значение результата (и, следовательно, тип defaultVal
), по-видимому, должен иметь статический член с именем TryParse
, который принимает string
и ссылку на изменяемый экземпляр этого типа и возвращает значение boolean
. Это ограничение становится явным благодаря заявленному типу возвращаемого значения в строке, начинающейся с : ^a when ...
. Тот факт, что defaultVal
сам по себе является возможным результатом, ограничивает его тип того же типа, что и ^a
. (Ограничение также подразумевается в другом месте функции, что необязательно, как объяснено ниже).: ^a when ^a : (static ....
описывает тип результата ^a
как наличие статического члена с именем TryParse типа string * ^a byref -> bool
. Другими словами, тип результата должен иметь статический член с именем TryParse
, который принимает string
, ссылку на свой экземпляр (и, следовательно, изменяемый экземпляр), и возвращает значение boolean
. Это описание показывает, как F# соответствует определению TryParse в .Net для типов DateTime, Int32, TimeSpan и т. д. Обратите внимание, что byref
является эквивалентом F# модификатора параметра out
или ref
в C#.let r = ref defaultVal
создает ссылочный тип и копирует в него предоставленное значение defaultVal
. ref
— это один из способов создания изменяемых типов в F#. Другой с ключевым словом mutable
. Разница в том, что mutable хранит свое значение в стеке, а ref хранит его в основной памяти/куче и содержит адрес (в стеке) для него. Последняя версия F# будет стремиться автоматически обновить изменяемые обозначения до ref в зависимости от контекста, что позволит вам кодировать только с точки зрения изменяемых.if (^a : (static...
— это инструкция if
по результатам вызова метода TryParse для статически выводимого типа ^a
. Этот TryParse передается, (text, &r.contents)
, по его подписи (string * ^a byref)
. Здесь &r.contents
предоставляет ссылку на изменяемое содержимое r
(имитирующее параметр C# out
или ref
) в соответствии с ожиданиями TryParse. Обратите внимание, что здесь мы не оговариваемся, и некоторые тонкости F# для взаимодействия с платформой .Net не распространяются так далеко; в частности, автоматическое свертывание разделенных пробелами параметров F# в параметры функции .net framework в виде кортежа недоступно. Следовательно, параметры предоставляются функции в виде кортежа (text, &r.contents)
.!r
- это то, как вы читаете эталонное значение. r.Value
тоже подойдет.Методы TryParse
, предоставляемые .Net, похоже, всегда устанавливают значение для параметра out. Следовательно, значение по умолчанию строго не требуется. Однако вам нужен держатель значения результата, r
, и он должен иметь начальное значение, даже нулевое. Мне не понравился ноль. Другой вариант, конечно, состоит в том, чтобы наложить другое ограничение на ^a
, которое требует некоторого свойства значения по умолчанию.
Следующее последующее решение устраняет необходимость в параметре по умолчанию, используя Unchecked.defaultof< ^a >
для получения подходящего значения заполнителя из предполагаемого типа результата (да, это похоже на волшебство). Он также использует тип Option
для характеристики успешного и неудачного получения значения результата. Таким образом, тип результата ^a option
.
tryParse
text
: ^a option when ^a : (static member TryParse : string * ^a byref -> bool)
=
let r = ref Unchecked.defaultof< ^a >
if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r.contents))
then Some (!r)
else None
И, согласно предложениям @kvb, возможна следующая краткость. В этом случае вывод типа используется как для определения ограничения типа для ^a
в результате его вызова в выражении if (^a : ...))
, так и для установления типа изменяемого буфера r
для выходного параметра TryParse. С тех пор я узнал, как FsControl делает некоторые это волшебство
let inline tryParse text : ^a option =
let mutable r = Unchecked.defaultof<_>
if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r))
then Some r
else None
let inline tryParseWithDefault defaultVal text : ^a =
match tryParse text with
| Some d -> d
| _ -> defaultVal
При этом использование будет...
> let x:DateTime option = tryParse "December 31, 2014";;
val x : DateTime option = Some 2014-12-31 12:00:00 a.m.
> let x:bool option = tryParse "false";;
val x : bool option = Some false
> let x:decimal option = tryParse "84.32";;
val x : decimal option = Some 84.32M
В случае использования ограничений типа для члена экземпляра, таких как тип, ограничивающий пользовательский оператор поиска динамического члена fsharp, ?
, так что тип цели должен содержать член FindName:string->obj
для использования в разрешении запросов, синтаксис выглядит следующим образом:
let inline (?) (targetObj:^a) (property:string) : 'b =
(^a : (member FindName:string -> obj) (targetObj, property)) :?> 'b
Примечание:
self
, который обычно является скрытым первым параметром.'b
Пример использования будет следующим:
let button : Button = window?myButton
let report : ReportViewer = window?reportViewer1
parse
github.com/gmpl/FSharpPlus/blob/
- person Gus; 16.10.2015
let mutable x = Unchecked.defaultof<_>
, а затем использование &x
в качестве аргумента для вызова метода кажется мне более чистым, чем введение фактического значения ref
; кроме того, сигнатура может быть выведена из определения (поэтому вам не нужно записывать ограничение дважды), хотя, возможно, вы включили его по педагогическим соображениям.
- person kvb; 16.10.2015