2017-02-23 20 views
9

Tôi có một vài loại tên miền trong mã của tôi mà tôi sử dụng để phân biệt các loại khác nhau của chuỗi, vì vậy trình biên dịch có thể ngăn cản tôi từ ví dụ qua các đối số theo thứ tự sai:F # loại bí danh cho chuỗi không nullable

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 

Tuy nhiên, điều này hiện không hoạt động một cách thỏa đáng, vì hai lý do:

  • tôi đã không thể tìm ra một cách để xác định rằng null là không phải là giá trị hợp lệ, vì vậy tôi không thể đảm bảo rằng phiên bản Foo sẽ không bao giờ là null.
  • Cả incantantions thực sự biên dịch (và chạy) - vì vậy tôi đã đạt được gì: D

Có cách nào để xác định bí danh kiểu đó

a) tham khảo/quấn cùng loại, nhưng không tương thích với nhau và b) không cho phép các giá trị null, ngay cả khi loại cơ bản sẽ cho phép?

+1

Tôi không biết cách nào để ngăn chặn 'null' như một giá trị tại compiletime. Xem thêm [SO câu hỏi này] (http://stackoverflow.com/questions/42341535/how-to-make-illegal-values-unrepresentable) –

Trả lời

10

biệt hiệu có thể được thay thế tự do nên không có cách nào để sử dụng chúng cho mục đích này, nhưng bạn có thể sử dụng đơn hợp cụ thể công đoàn phân biệt đối xử để thay thế. Với các nhà xây dựng thông minh ngăn chặn việc sử dụng các triển khai rỗng và riêng (để mã bên ngoài mô-đun mà chúng được định nghĩa không thể đi xung quanh các nhà xây dựng thông minh), về cơ bản bạn sẽ có được những gì bạn muốn (mặc dù việc kiểm tra null được thực thi tại thời gian chạy) chứ không phải là thời gian biên dịch, thật đáng buồn):

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

Xem http://fsharpforfunandprofit.com/posts/designing-with-types- single-case-dus/cho một điều trị chuyên sâu xử lý nó theo cách này. – mydogisbox

+0

Không có kiểm tra null tại thời gian biên dịch là tất nhiên không tốt như có chúng, nhưng với phương pháp này họ có thể được đẩy trở lại ranh giới ứng dụng (nơi chúng tôi vẫn không nên tin tưởng bất cứ điều gì ...) và cho phép tên miền logic không bị ô nhiễm bởi giả định giá trị null có thể. –

1

một biến thể của câu trả lời @kvb là sử dụng một tuổi-timer lừa từ C++ dựa trên các loại "tag" để tạo bí danh phân biệt (C++ typedefs được bí danh vì thế bị cùng lợi ích và hạn chế như bí danh loại F #)

Ngoài ra F # 4 không hỗ trợ cấu trúc ADT (nhưng F # 4.1) nên sử dụng ADT tạo nhiều đối tượng hơn trên heap. Ví dụ của tôi sử dụng các kiểu struct để giảm thiểu áp lực heap.

Trong sở thích cá nhân của riêng tôi, tôi xem xét một chuỗi rỗng là "tương tự" như chuỗi rỗng vì vậy tôi nghĩ thay vì ném người ta có thể đối xử với null như trống rỗng.

// 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

Đây là một biến thể nhỏ của câu trả lời của @ FuleSnabel, sử dụng cái mà chúng tôi gọi là 'loại ma'. Tôi sẽ thể hiện chúng như sau, mà tôi nghĩ là một chút nhiều thành ngữ:

/// 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" 

Với các định nghĩa trên, chúng ta hãy cố gắng thử nghiệm của bạn:

> 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'