2012-12-18 10 views
19

Supponiamo ho voluto creare un tipo di record che rappresenta accettabile min/max: limitiÈ possibile far rispettare che un record rispetta alcuni invarianti?

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

c'è un modo per far rispettare che Min < Max? È facile scrivere una funzione validateBounds, mi chiedevo solo se esistesse un modo migliore per farlo.

Edit: mi sono reso conto che per questo esempio specifico ho potuto probabilmente ottenere via con esponendo due proprietà e ri-ordinare gli argomenti, quindi diciamo che stavamo cercando di fare

type Person = { Name: string } 

e nome ha bisogno di avere a almeno un personaggio.

+0

Se non mi sbaglio, dischi non ti danno un costruttore esplicito. – Mathias

+0

No, avresti bisogno di un sistema di tipi più forte come i tipi di raffinatezza in [F *] (http://rise4fun.com/FStar). I Contratti di codice possono far valere questo invariante in C#, ma AFAIK il controllore contratto Contratti di codice continua a non funzionare correttamente per i progetti F #. –

+0

Si può prendere in considerazione il passaggio a strutture che offrono uguaglianza strutturale. La corrispondenza tra pattern può essere facilitata attraverso pattern attivi. Il vantaggio è che è facile applicare le varianti [utilizzando costruttori espliciti e quindi costruisci, ecc.] (Http://stackoverflow.com/questions/12600574/argument-validation-in-f-struct-constructor). – pad

risposta

11

Ecco un'altra soluzione basata su livelli di protezione:

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

Questo è intelligente - non sapevo che si potesse rendere privato il "Costruttore". – Mathias

+0

Non è scritto in [MSDN] (http://msdn.microsoft.com/en-us/library/dd233184.aspx), ma se provate 'type Bounds = {private min: float}', il compiler out : * errore FS0575: i modificatori di accessibilità non sono consentiti sui campi dei record. Usa 'tipo R = interno ...' o 'tipo R = privato ...' per dare l'accessibilità all'intera rappresentazione. * – bytebuster

+2

@Mathias: tieni presente che perdi la maggior parte dei vantaggi dei record quando i campi sono privati . Tanto vale usare una classe. – Daniel

5

penso che la cosa migliore è un membro statico:

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} 

e utilizzarlo come

> 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;} 

Tuttavia, come fai notare, il grande rovescio della medaglia di questo approccio non è che nulla impedisce la costruzione di un record "non valido" direttamente. Se questa è una grande preoccupazione, considerare l'utilizzo di un tipo di classe per garantire le vostre invarianti:

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 

insieme con un modello attivo per convenienza simile a quello che si ottiene con i record (in particolare per quanto riguarda i pattern matching):

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

tutti insieme:

> 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

Stavo considerando questo - mi sembra un metodo di fabbrica. L'unico problema, tuttavia, è che nulla impedisce a un chiamante di ignorare il metodo Create e creare un'istanza di un record "non valido". O mi sta sfuggendo qualcosa? – Mathias

+0

No, sei assolutamente corretto, il metodo Create potrebbe sempre essere aggirato. È certamente un grosso problema, abbastanza da prendere in considerazione la possibilità di rendere privato il record o di optare per un tipo di classe più complesso. –

+0

Grazie per la risposta!È divertente, perché in passato avevo le stesse lamentele con la struttura C# e il suo costruttore vuoto sempre presente, che causa anche mal di testa di convalida. – Mathias

0

una soluzione poco raccomandabile per l'esempio di stringa - utilizzare un DU

012.
type cleverstring = |S of char * string 

In questo modo la stringa avrà almeno un carattere. Quindi puoi semplicemente usare cleverstring invece di string nel tuo record, anche se probabilmente vorrai scrivere alcune funzioni wrapper per farlo apparire come una stringa.

+3

Oh - che è deliziosamente brutto :) – Mathias

+1

In realtà ha un po 'più senso quando lo si usa in un parser –

Problemi correlati