2017-02-23 35 views
9

我在我的代碼中有幾個域類型用於區分不同類型的字符串,因此編譯器可以阻止我從例如傳遞參數順序錯誤:非可空字符串的F#類型別名

type Foo = string 
type Bar = string 

let baz (foo : Foo) (bar : Bar) = printfn "%A %A" foo bar 

let f : Foo = "foo" 
let b : Bar = "bar" 

baz f b // this should be OK 
baz b f // this shouldn't compile 

然而,這目前還不令人滿意地工作,原因有二:

  • 我一直沒能想出一個辦法來指定null是不是有效的值,所以我不能保證Foo實例永遠不會是null
  • 兩個incantantions實際編譯(並運行) - 所以我一無所獲:d

有沒有一種方法來定義類型別名

一)參考/包同一類型,但與彼此不兼容,並且b)不允許null值,即使基礎類型允許它?

+1

我不知道的防止任何方式'null'作爲編譯時的值。另請參閱[此SO問題](http://stackoverflow.com/questions/42341535/how-to-make-illegal-values-unrepresentable) –

回答

10

別名可以自由替換,因此無法將它們用於此目的,但可以使用單例歧義聯合。有了防止使用空和私有實現的智能構造函數(因此,定義它們的模塊之外的代碼不能繞過智能構造函數),基本上應該得到你想要的(儘管在運行時檢查null是強制的而不是編譯時間,不幸的是):

type Foo = private Foo of string with 
    static member OfString(s) = 
     if s = null then failwith "Can't create null Foo" 
     else Foo s 

type Bar = private Bar of string with 
    static member OfString(s) = 
     if s = null then failwith "Can't create null Bar" 
     else Bar s 

let baz (foo : Foo) (bar : Bar) = printfn "%A %A" foo bar 
let f = Foo.OfString "foo" 
let b = Bar.OfString "bar" 
baz f b // ok 
baz b f // type error 
+3

請參閱http://fsharpforfunandprofit.com/posts/designing-with-types-單個案例/深入處理這種處理方式。 – mydogisbox

+0

在編譯時沒有空檢查當然不如擁有它們,但通過這種方法,它們可以被推回到應用程序邊界(我們仍然不應該信任任何東西......)並允許域通過假設可能的空值來使邏輯不受污染。 –

1

@kvb答案的變體是使用從C++,它依賴於「標籤」類型來創建傑出的別名一箇舊計時器特技(C++的typedef是別名因此遭受與F#類型別名相同的優點和缺點)

另外F#4不支持struct ADT(但F#4.1),所以使用ADT在堆上創建更多對象。我的例子使用結構類型來緩解堆壓力。

在我個人的偏好中,我認爲空字符串與空字符串「相同」,所以我認爲不是拋出一個可以將null視爲空。

// NonNullString coalesces null values into empty strings 
type NonNullString<'Tag>(s : string) = 
    struct 
    member x.AsString  = if s <> null then s else "" 
    override x.ToString() = x.AsString 
    static member OfString s = NonNullString<'Tag> s 
    end 

// Some tags that will be used when we create the type aliases 
type FooTag = FooTag 
type BarTag = BarTag 

// The type aliases 
type Foo = NonNullString<FooTag> 
type Bar = NonNullString<BarTag> 

// The function 
let baz (foo : Foo) (bar : Bar) = printfn "%A, %A" foo.AsString.Length bar.AsString.Length 

[<EntryPoint>] 
let main argv = 
    // Some tests 
    baz (Foo.OfString null) (Bar.OfString "Hello") 
    // Won't compile 
    // baz (Bar.OfString null) (Bar.OfString "Hello") 
    // baz "" (Bar.OfString "Hello") 
    0 
0

這是@ FuleSnabel的答案的一個微小變體,它使用了我們所說的'幻像類型'。我想表達他們像下面這樣,我認爲這是一個有點更地道:

/// Strongly-typed strings. 
module String_t = 
    type 'a t = private T of string 

    let of_string<'a> (s : string) : 'a t = T s 
    let to_string (T s) = s 

type foo = interface end 
type bar = interface end 

let baz (foo : foo String_t.t) (bar : bar String_t.t) = 
    printfn "%s %s" (String_t.to_string foo) (String_t.to_string bar) 

let f : foo String_t.t = String_t.of_string<foo> "foo" 
let b : bar String_t.t = String_t.of_string<bar> "bar" 

有了上面的定義,讓我們試試你的測試:

> baz f b;; 
foo bar 
val it : unit =() 
> baz b f;; 

    baz b f;; 
    ----^ 

/path/to/stdin(16,5): error FS0001: Type mismatch. Expecting a 
    'foo String_t.t'  
but given a 
    'bar String_t.t'  
The type 'foo' does not match the type 'bar' 
相關問題