F# type alias for non-nullable string

走远了吗. 提交于 2021-02-20 06:06:32

问题


I have a couple of domain types in my code that I use to distinguish different types of strings, so the compiler can stop me from e.g. passing arguments in the wrong order:

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

However, this currently doesn't work satisfactorily, for two reasons:

  • I haven't been able to figure out a way to specify that null is not a valid value, so I can't guarantee that a Foo instance will never be null.
  • Both incantantions actually compile (and run) - so I've gained nothing :D

Is there a way to define type aliases that

a) refer to/wrap the same type, but are incompatible with each-other, and b) disallow null values, even if the underlying type would allow it?


回答1:


Aliases can be substituted freely so there's no way to use them for this purpose, but you can use single-case discriminated unions instead. With smart constructors that prevent using null and private implementations (so that code outside of the module where they're defined can't go around the smart constructors), you should basically get what you want (although the checking for null is enforced at runtime rather than compile time, sadly):

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



回答2:


A variant of @kvb answer is to use an old-timer trick from C++ that relies on "tag" types to create distinguished aliases (C++ typedefs are aliases thus suffer from same benefits and drawbacks as F# type aliases)

Also F#4 don't support struct ADTs (but F#4.1 does) so using ADTs create more objects on heap. My example uses struct types to mitigate heap pressure.

In my own personal preference I consider a null string to be the "same" as the empty string so I think instead of throwing one could treat null as empty.

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



回答3:


Here's a slight variant of @FuleSnabel's answer, which is using what we call 'phantom types'. I would express them like the following, which I think is a bit more idiomatic:

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

With the above definitions, let's try your 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'


来源:https://stackoverflow.com/questions/42427189/f-type-alias-for-non-nullable-string

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!