Tak . Witaj w świecie ograniczeń członków, ref i wartości 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
i text
są parametry formalne i będzie wnioskować. Tutaj, text
jest już ograniczony do string
, ponieważ jest używany jako pierwszy parametr w wywołaniu metody statycznej, SomeType.TryParse
, jak wyjaśniono później.
^a
jest statycznie rozdzielonym parametrem typu (w porównaniu z ogólnym typem parametru w postaci 'a
). ^a
zostanie rozwiązany w czasie kompilacji do określonego typu. W konsekwencji funkcja, która go obsługuje, musi być oznaczona jako inline
, co oznacza, że każde wywołanie funkcji stanie się zastępacją tego wywołania w miejscu w stosunku do rzeczywistej treści funkcji, przy czym każdy parametr typu statycznego stanie się określonym typem; w tym przypadku, niezależnie od typu: defaultVal
. Nie ma ograniczeń typu bazowego ani typu interfejsu ograniczających możliwy typ defaultVal
. Można jednak zapewnić ograniczenia statyczne i członków instancji, tak jak tutaj. W szczególności wartość wyniku (a zatem defaultVal
) musi mieć najwyraźniej statyczny element o nazwie TryParse
, który akceptuje wartość string
, odwołanie do zmiennej instancji tego typu i zwraca wartość boolean
. Ograniczenie to jest jednoznaczne z zastrzeżeniem określonego typu zwrotu w linii zaczynającej się od : ^a when ...
. Sam fakt, że defaultVal
jest możliwy, ogranicza go do tego samego typu, co ^a
. (Ograniczenie jest również ukryte w innym miejscu całej funkcji).
: ^a when ^a : (static ....
opisuje typ wyniku, ^a
, jako element statyczny o nazwie TryParse typu string * ^a byref -> bool
. Oznacza to, że typ wyniku będzie miał statyczny element członkowski, który akceptuje wartość string
, odwołanie do samej instancji (a zatem zmienne) i zwróci wartość boolean
. Ten opis jest taki, jak F # pasuje do definicji .Net TryParse na typy DateTime, Int32, TimeSpan itp. Uwaga: byref
jest odpowiednikiem F # C# 's out
lub ref
modyfikatora parametru.
let r = ref defaultVal
tworzy typ referencyjny i kopiuje podaną wartość, defaultVal
, do niego. ref
to jeden ze sposobów, w jaki F # tworzy typy zmienne. Drugi jest ze słowem kluczowym mutable
. Różnica polega na tym, że mutable przechowuje swoją wartość na stosie, podczas gdy ref przechowuje ją w głównej pamięci/stercie i przechowuje do niej adres (na stosie). Najnowsza wersja F # będzie starała się automatycznie uaktualniać zmienne oznaczenia do ref w zależności od kontekstu, pozwalając ci kodować tylko w kategoriach zmienności.
- to instrukcja dotycząca wyników wywołania metody TryParse na typ wywnioskowany statycznie,
^a
. Ta właściwość TryParse jest przekazywana, (text, &r.contents)
, według jej sygnatury (string * ^a byref)
. W tym przypadku, &r.contents
zapewnia odniesienie do zmiennej zawartości r
(symulowanie parametru C# 's out
lub ref
) na oczekiwanie TryParse. Zauważ, że jesteśmy tutaj wyłączeni, a niektóre F # niceties dla współdziałania ze strukturą .Net nie rozciągają się aż tak daleko, w szczególności automatyczne zwijanie oddzielonych spacjami F # parametrów na.parametry funkcji szkieletu sieci jako krotka. W związku z tym parametry są dostarczane do funkcji jako krotka, (text, &r.contents)
.
!r
to sposób odczytywania wartości odniesienia. r.Value
również by działało.
Metody dostarczane przez .Net wydają się zawsze ustawiać wartość dla parametru out. W związku z tym wartość domyślna nie jest ściśle wymagana. Potrzebny jest jednak posiadacz wartości wyniku, r
, który musi mieć wartość początkową, nawet zero. Nie podobało mi się zero. Inną opcją, oczywiście, jest nałożenie kolejnego ograniczenia na ^a
, które wymaga pewnej wartości domyślnej.
Następujące kolejne rozwiązanie usuwa potrzebę parametru domyślnego, używając Unchecked.defaultof<^a>
do wyprowadzenia odpowiedniej wartości symbolu zastępczego z typu "wnioskiem o wyniku" (tak, czuje się jak magia). Używa również typu Option
, aby scharakteryzować sukces i porażkę uzyskując wartość wyniku. Typ wyniku to zatem ^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
I, na sugestie @kvb, możliwa jest następująca zwięzłość. W tym przypadku, wnioskowanie o typie jest stosowane zarówno do określenia ograniczenia typu na ^a
w wyniku jego wywołania w wyrażeniu if (^a : ...))
, jak również do ustalenia typu zmiennego bufora r
dla parametru wyjściowego TryParse. I have since come to learn this is how FsControl does some of it's magic
let inline tryParseWithDefault defaultVal text : ^a option =
let mutable r = defaultVal
if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r))
then Some r
else None
let inline tryParse text = tryParseWithDefault (Unchecked.defaultof<_>) text
W przypadku zastosowania ograniczenia typu na członka przykład takich jak dynamiczny element odnośników niestandardowego operatora TYP usztywniająca fsharp'S, ?
taki sposób, że typ przedmiotu musi zawierać element FindName:string->obj
składnia jest następujący:
let inline (?) (instanceObj:^A) (property:string) : 'b =
(^A : (member FindName:string -> obj) (instanceObj, property)) :?> 'b
Uwaga:
- rzeczywista podpis metody instancji jednoznacznie określa
self
obiekt, który normalnie jest ukryty pierwszy parametr
- Takie rozwiązanie sprzyja także bez względu na wynik,
'b
przykład użycia byłby następujący:
let button : Button = window?myButton
let report : ReportViewer = window?reportViewer1
Zobacz również http: // stackoverflow. com/q/4656864/82959. – kvb