2015-10-16 29 views

Odpowiedz

10

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 
  1. 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.
  2. ^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).
  3. : ^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.
  4. 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.
  5. 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).
  6. !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:

  1. rzeczywista podpis metody instancji jednoznacznie określa self obiekt, który normalnie jest ukryty pierwszy parametr
  2. 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 
+1

W języku F # + jest to funkcja zdefiniowana w podobny sposób, a także częściowa wersja 'parsowanie' https://github.com/gmpl/FSharpPlus/blob/24f6501d7c6449e0eb06c993ad247c8f1db5a3f6/FSharpPlus/Operators.fs#L261 – Gustavo

+0

Jako drobny komentarz w stylu , użycie 'let mutable x = Unchecked.defaultof <_>' a następnie użycie '& x' jako argumentu do wywołania metody wydaje mi się czystsze niż wprowadzenie rzeczywistej wartości' ref'; również sygnaturę można wywnioskować z definicji (nie musisz więc dwukrotnie pisać wiązania), ale być może uwzględniłeś to ze względów pedagogicznych. – kvb

+0

@Gustavo Nie wiedziałem o projekcie FSharpPlus i tylko przejściowo z FsControl. Dziękuję za otwarcie moich oczu. Zdefiniowali TryParse jest podobny, ale bardziej elegancki sposób :) https://github.com/gmpl/FsControl/blob/dc8b41a5b7f50f5adc419512df8efce0801c4351/FsControl.Core/Converter.fs#L86 – George