非可空字符串的F#类型别名

时间:2017-02-23 22:12:41

标签: 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实例永远不会{ {1}}。
  • 两个incantantions实际编译(并运行) - 所以我没有得到任何东西:D

有没有办法定义

类型的别名

a)引用/包装相同的类型,但彼此不兼容,并且 b)不允许null值,即使基础类型允许它?

2 个答案:

答案 0 :(得分:1)

@kvb答案的一个变体是使用来自C ++的老式技巧,它依赖于“标签”类型来创建显着的别名(C ++ typedef是别名因此具有与F#类别名相同的优点和缺点)

F#4也不支持struct ADT(但F#4.1会这样做),因此使用ADT会在堆上创建更多对象。我的示例使用struct类型来缓解堆压力。

根据我个人的偏好,我认为空字符串与空字符串“相同”,所以我认为不是抛出一个就可以将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

答案 1 :(得分: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'