2012-12-18 14 views
19

Załóżmy, że chciał stworzyć typ rekordu, który reprezentuje dopuszczalne granice Min/Max:Czy możliwe jest egzekwowanie przestrzegania przez Rekordy niektórych niezmienników?

type Bounds = { Min: float; Max: float } 

Czy istnieje sposób, aby wymusić że Min < Max? Łatwo jest napisać funkcję validateBounds, zastanawiałem się tylko, czy istnieje lepszy sposób na zrobienie tego.

Edit: zdałem sobie sprawę, że w tym konkretnym przykładzie prawdopodobnie mógłbym uciec wystawiając dwie właściwości i re-order argumenty, więc powiedzmy, że staraliśmy się zrobić

type Person = { Name: string } 

i nazwa musi mieć co przynajmniej jeden znak.

+0

O ile się nie mylę, płyty nie daje jednoznacznej konstruktora. – Mathias

+0

Nie, potrzebujesz systemu typu silniejszego, takiego jak typy udoskonalania w [F *] (http://rise4fun.com/FStar). Kontrakt Code może wymusić ten niezmiennik w C#, ale AFAIK, sprawdzanie kontraktu Code Contracts, nadal nie działa poprawnie dla projektów F #. –

+0

Możesz rozważyć przestawienie się na struktury, które również zapewniają strukturalną równość. Dopasowywanie wzorców można ułatwić poprzez aktywne wzorce. Zaletą jest to, że można łatwo wymusić warianty [za pomocą jawnych konstruktorów, a następnie konstruować, itp.] (Http://stackoverflow.com/questions/12600574/argument-validation-in-f-struct-constructor). – pad

Odpowiedz

11

Oto kolejny rozwiązanie oparte na poziomach ochrony:

module MyModule = 
    type Bounds = private { _min: float; _max: float } with 
     // define accessors, a bit overhead 
     member public this.Min = this._min 
     member public this.Max = this._max 
     static member public Make(min, max) = 
      if min > max then raise (ArgumentException("bad values")) 
      {_min=min; _max=max} 

    // The following line compiles fine, 
    // e.g. within your module you can do "unsafe" initialization 
    let myBadBounds = {_min=10.0; _max=5.0} 

open MyModule 
let b1 = Bounds.Make(10.0, 20.0) // compiles fine 
let b1Min = b1.Min 
let b2 = Bounds.Make(10.0, 5.0) // throws an exception 
// The following line does not compile: the union cases of the type 'Bounds' 
// are not accessible from this code location 
let b3 = {_min=10.0; _max=20.0} 
// The following line takes the "bad" value from the module 
let b4 = MyModule.myBadBounds 
+1

To jest sprytne - nie wiedziałem, że możesz uczynić "Konstruktora" prywatnym. – Mathias

+0

Nie jest napisany w [MSDN] (http://msdn.microsoft.com/en-us/library/dd233184.aspx), ale jeśli wypróbujesz 'type Bounds = {private min: float}', kompilator out : * error FS0575: Modyfikatory dostępności nie są dozwolone w polach rekordów. Użyj "type R = internal ..." lub "type R = private ...", aby zapewnić dostęp do całej reprezentacji. * – bytebuster

+2

@Mathias: Pamiętaj, że tracisz większość korzyści z rekordów, gdy pola są prywatne . Równie dobrze można użyć klasy. – Daniel

5

myślę najlepiej jest statyczny element:

type Bounds = { Min: float; Max: float } 
    with 
     static member Create(min: float, max:float) = 
      if min >= max then 
       invalidArg "min" "min must be less than max" 

      {Min=min; Max=max} 

i używać go jak

> Bounds.Create(3.1, 2.1);; 
System.ArgumentException: min must be less than max 
Parameter name: min 
    at FSI_0003.Bounds.Create(Double min, Double max) in C:\Users\Stephen\Documents\Visual Studio 2010\Projects\FsOverflow\FsOverflow\Script2.fsx:line 5 
    at <StartupCode$FSI_0005>[email protected]() 
Stopped due to error 
> Bounds.Create(1.1, 2.1);; 
val it : Bounds = {Min = 1.1; 
        Max = 2.1;} 

Jednakże, jak podkreślić, wielki dół stroną tego podejścia jest, że nic nie stoi na przeszkodzie, aby bezpośrednio stworzyć "nieważny" rekord. Jeśli jest to poważny problem, należy rozważyć użycie typu klasy dla zagwarantowania swoich niezmienników:

type Bounds(min:float, max:float) = 
    do 
     if min >= max then 
      invalidArg "min" "min must be less than max" 

    with 
     member __.Min = min 
     member __.Max = max 

razem z aktywnym wzorzec dla wygody podobny do tego, co można uzyskać z dokumentacji (w szczególności w odniesieniu do dopasowywania wzorca):

let (|Bounds|) (x:Bounds) = 
    (x.Min, x.Max) 

wszystko razem:

> let bounds = Bounds(2.3, 1.3);; 
System.ArgumentException: min must be less than max 
Parameter name: min 
    at FSI_0002.Bounds..ctor(Double min, Double max) in C:\Users\Stephen\Documents\Visual Studio 2010\Projects\FsOverflow\FsOverflow\Script2.fsx:line 4 
    at <StartupCode$FSI_0003>[email protected]() 
Stopped due to error 
> let bounds = Bounds(1.3, 2.3);; 

val bounds : Bounds 

> let isMatch = match bounds with Bounds(1.3, 2.3) -> "yes!" | _ -> "no";; 

val isMatch : string = "yes!" 

> let isMatch = match bounds with Bounds(0.3, 2.3) -> "yes!" | _ -> "no";; 

val isMatch : string = "no" 
+0

Rozważałem to - wygląda mi to jak metoda fabryczna. Jedynym problemem jest to, że nic nie uniemożliwia rozmówcy ominięcia metody Create i utworzenia "nieważnego" rekordu. Czy może czegoś brakuje? – Mathias

+0

Nie, jesteś absolutnie poprawny, metoda Create zawsze może być ominięta. Jest to z pewnością poważny problem, na tyle, że możesz rozważyć uczynienie rekordu prywatnym lub w inny sposób zdecydować się na bardziej złożony typ zajęć. –

+0

Dzięki za odpowiedź!To zabawne, ponieważ miałem podobne zastrzeżenia w przeszłości z C# struct i jego zawsze obecnym pustym konstruktorem, który również powoduje bóle walidacyjne. – Mathias

0

na podejrzanie rozwiązanie na przykład String - użycie DU

type cleverstring = |S of char * string 

Spowoduje to, że ciąg będzie miał co najmniej jeden znak. Wtedy możesz użyć tylko cleverstring zamiast string w swoim rekordzie, chociaż prawdopodobnie chcesz napisać niektóre funkcje opakowania, aby wyglądał jak ciąg znaków.

+3

Oh - to jest cudownie brzydkie :) – Mathias

+1

To faktycznie ma trochę więcej sensu, kiedy używasz go w parserze –

Powiązane problemy