2015-12-10 4 views
6

Amo la semplicità di tipi cometipo per rappresentare una stringa che non è vuoto o spazi in F #

type Code = Code of string 

ma vorrei mettere alcune restrizioni stringa (in questo caso - non consentono vuota stringhe solo spazi). Qualcosa come

type nonemptystring = ??? 
type Code = Code of nonemptystring 

Come si definisce questo tipo in modo idiatico F #? So che posso renderlo una classe con costruttore o un modulo limitato con funzione di fabbrica, ma c'è un modo semplice?

+3

Dai un'occhiata a questo post: http://fsharpforfunandprofit.com/posts/designing-with-types-more-semantic-types/ - la sezione "Modellazione di stringhe vincolate con tipi". Probabilmente avrai bisogno di fare qualcosa del genere – Petr

+0

@Petr Ho visto questo post ma trovo questa soluzione un po 'complicata. Grazie per il collegamento comunque. – Mikhail

+0

Sì, sono d'accordo che quel post è un po 'complicato. Ho alcuni esempi migliori qui: https://gist.github.com/swlaschin/54cfff886669ccab895a. Come altri hanno sottolineato, le soluzioni non sono particolarmente eleganti. OTOH, dovresti fare qualcosa di simile anche nel codice OO. – Grundoon

risposta

0

Sfortunatamente non esiste una sintassi comoda per dichiarare un sottoinsieme limitato di tipi, ma vorrei sfruttare i pattern attivi per farlo. Come dici giustamente, si può fare un tipo e verificare la validità è quando si costruisce è:

/// String type which can't be null or whitespace 
type FullString (string) = 
    let string = 
     match (System.String.IsNullOrWhiteSpace string) with 
     |true -> invalidArg "string" "string cannot be null or whitespace" 
     |false -> string 
    member this.String = string 

Ora, la costruzione di questo tipo ingenuamente possono generare eccezioni di runtime e non vogliamo che! Quindi cerchiamo di usare modelli attivi:

let (|FullStr|WhitespaceStr|NullStr|) (str : string) = 
    match str with 
    |null -> NullStr 
    |str when System.String.IsNullOrWhiteSpace str -> WhitespaceStr 
    |str -> FullStr(FullString(str)) 

Ora abbiamo qualcosa che possiamo usare con la sintassi pattern matching per costruire le nostre FullString s. Questa funzione è sicura in fase di esecuzione perché creiamo solo un FullString se siamo nel caso valido.

È possibile utilizzare in questo modo:

let printString str = 
    match str with 
    |NullStr -> printfn "The string is null" 
    |WhitespaceStr -> printfn "The string is whitespace" 
    |FullStr fstr -> printfn "The string is %s" (fstr.String) 
+0

Grazie per questo esempio. Ho due dubbi: 1. Dovrò usare ".String" ogni volta che lo uso da qualche parte che sembra imbarazzante. 2. Dovrei sovrascrivere Equals e cosa no per questo tipo di FullString. – Mikhail

+0

@Mikhail Potresti usare un record invece di una classe che ti darebbe un confronto strutturale automatico, l'uguaglianza, ecc. Ma dovresti fare un po 'più di trucchi per assicurarti che il record non possa mai contenere una stringa non valida. Detto questo, questi override dovrebbero essere abbastanza semplici poiché puoi semplicemente reindirizzare direttamente ai metodi della stringa. – TheInnerLight

3

Un string è essenzialmente una sequenza di valori char (in Haskell, BTW, Stringè un tipo alias per [Char]). Una domanda più generale, quindi, sarebbe se fosse possibile dichiarare staticamente un elenco con una determinata dimensione.

Tale lingua è nota come Dependent Types e F # non ce l'ha. La risposta breve, quindi, è che non è possibile farlo in modo dichiarativo.

Il metodo più semplice, e probabilmente anche più idiomatica, modo, allora, sarebbe definire Code come un unico caso Discriminazione in Union:

type Code = Code of string 

Nel modulo che definisce Code, devi anche definire un funzione che i clienti possono utilizzare per creare Code valori:

let tryCreateCode candidate = 
    if System.String.IsNullOrWhiteSpace candidate 
    then None 
    else Some (Code candidate) 

Questa funzione contiene la logica runtime che impedisce ai client di creare vuoti Code valori:

> tryCreateCode "foo";; 
val it : Code option = Some (Code "foo") 
> tryCreateCode "";; 
val it : Code option = None 
> tryCreateCode " ";; 
val it : Code option = None 

Cosa impedisce a un client di creare un valore Code non valido, quindi? Ad esempio, un client non potrebbe aggirare la funzione tryCreateCode e scrivere semplicemente Code ""?

Questo è dove signature files entra.Si crea un file di firma (.fsi), e in che dichiarano tipi e le funzioni in questo modo:

type Code 
val tryCreateCode : string -> Code option 

Qui, il tipo Code è dichiarato, ma la sua 'costruttore' non lo è. Ciò significa che non puoi creare direttamente valori di questo tipo. Questo, ad esempio, non compila:

Code "" 

L'errore dato è:

errore FS0039: Il valore, costruttore dello spazio dei nomi o il tipo di 'codice' non è definito

L'unico modo per creare un valore Code è utilizzare la funzione tryCreateCode.

Come dato qui, non è più possibile accedere al valore di stringa sottostante Code, a meno che non si fornisce anche una funzione per questo:

let toString (Code x) = x 

e dichiararla nello stesso file .fsi come sopra:

val toString : Code -> string 

Potrebbe sembrare molto lavoro, ma in realtà sono solo sei righe di codice e tre righe di tipo dichiarazione (nel file .fsi).

+1

Non rendere il tipo constructor private un'alternativa? 'type Code = private Code of string' –

+0

@NikosBaxevanis Non sapevo che potessi farlo: $ Grazie! Ciò elimina la necessità di un file '.fsi', ma il resto della mia risposta si applica ancora. –

Problemi correlati