2017-02-23 13 views
9

Mam kilka typów domen w moim kodzie, których używam do rozróżniania różnych typów łańcuchów, więc kompilator może mnie powstrzymać od np. przekazując argumenty w niewłaściwej kolejności:Alias ​​typu F # dla ciągów nienaprawnych

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 

to jednak obecnie nie działa w sposób zadowalający, z dwóch powodów:

  • nie byłem w stanie wymyślić sposób, aby określić, że jest null nie jest to poprawna wartość, więc nie mogę zagwarantować, że instancja Foo nigdy nie będzie null.
  • Oba incantantions faktycznie kompilować (i działać) - tak ja otrzymałem nic: D

Czy istnieje sposób, aby określić typ aliasów że

a) odnoszą się do/owinąć tego samego typu, ale są niekompatybilne ze sobą, a wartości odrzucają, nawet jeśli typ bazowy pozwoliłby na to?

+1

nie jestem świadomy jakikolwiek sposób zapobiegania 'null' jako wartość w czasie kompilacji. Zobacz także [to pytanie SO] (http://stackoverflow.com/questions/42341535/how-to-make-illegal-values-reprepresentable) –

Odpowiedz

10

Aliasy można dowolnie zastępować, więc nie ma możliwości ich użycia w tym celu, ale zamiast tego można użyć pojedynczych przypadków dyskryminowanych. W przypadku inteligentnych konstruktorów, które uniemożliwiają stosowanie zerowych i prywatnych implementacji (tak, że kod spoza modułu, w którym są zdefiniowane, nie może ominąć inteligentnych konstruktorów), powinieneś zasadniczo uzyskać to, czego potrzebujesz (chociaż sprawdzanie wartości zerowej jest wymuszane w środowisku wykonawczym zamiast kompilacji, niestety):

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

Zobacz http://fsharpforfunandprofit.com/posts/designing-with-types- single-case-dus/dla dogłębnego traktowania obsługi w ten sposób. – mydogisbox

+0

Brak sprawdzania wartości null podczas kompilacji nie jest oczywiście tak dobry jak ich posiadanie, ale za pomocą tej metody można je przesłać z powrotem do granicy aplikacji (gdzie nadal nie powinniśmy ufać czemukolwiek ...) i zezwalać na domenę logika pozostanie niezanieczyszczona przez przyjęcie możliwej wartości pustej. –

1

wariant @kvb odpowiedź jest użycie sztuczki staruszek z C++, który opiera się na typach „tag”, aby stworzyć wyróżniające aliasów (C++ typedefs są aliasy więc cierpią te same zalety i wady, jak aliasy typu F #)

Również F # 4 nie obsługuje strukturalnych ADT (ale F # 4.1 robi), więc używanie ADT tworzy więcej obiektów na stercie. Mój przykład wykorzystuje typy struktur, aby złagodzić presję sterty.

W mojej osobistej preferencji uważam ciąg zerowy za "taki sam" jak pusty ciąg, więc myślę, że zamiast rzucać można traktować zero jako puste.

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

Oto niewielki wariant odpowiedzi @ FuleSnabela, który wykorzystuje to, co nazywamy "typem widmowym". Chciałbym wyrazić je jak poniżej, który moim zdaniem jest nieco bardziej idiomatyczne:

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

Z powyższych definicji, spróbujmy test:

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