我可以在单一案例歧视联盟中调用“别名”类型的方法吗?

时间:2017-03-27 13:25:40

标签: f# discriminated-union

我在优秀的F# for Fun and Profit中读到了我可以使用单一案例的歧视联盟,除其他外,还要获得类型安全性。所以我试着这样做。但是我很快发现我无法调用DU的别名或查看类型的方法。

以下是一个例子:

type MyList = MyList of List<int>
let list' = [1; 2; 3]
printfn "The list length is %i" list'.Length
let myList = MyList([1; 2; 3])
// The list length is 3
// type MyList = | MyList of List<int>
// val list' : int list = [1; 2; 3]
// val myList : MyList = MyList [1; 2; 3]

到目前为止一切顺利。然后我试了

myList.Length
// Program.fs(12,8): error FS0039: The field, constructor or member 'Length' is not defined.

当使用单例DU时,是否有一种简单的方法可以访问“别名”或“已查看”类型的方法?

3 个答案:

答案 0 :(得分:3)

这不是你所谓的“别名”。你已经声明了一个全新的类型,它将一个列表包装在其中。你的类型不仅仅是列表的另一个名称,它是一个包装器。

如果要声明真正的别名,则应使用语法type A = B,例如:

type MyList = List<int> 
let myList : MyList = [1; 2; 3]
printf "Length is %d" myList.Length

使用此类别名,MyList将100%替代List<int>,反之亦然。这就是“别名”的意思。同一事物的名称不同。

另一方面,包装器将为您提供额外的类型安全性:如果您尝试使用期望List<int>的裸MyList,编译器将会捕获。这就是通常使用这种包装的方式。但是这种额外的保护需要付出代价:你不能只使用MyList,就好像它是一个列表一样。因为这会扼杀整个想法。不能两种方式。

问:等等,你的意思是说我必须重新实现所有列表功能才能得到这个包装器吗?

嗯,不。如果您移动方法并转向函数,则可以通过提供map

来实现此包装/展开的通用性
type MyList = MyList of List<int>
   with static member map f (MyList l) = f l

let myList = MyList [ 1; 2; 3 ]
let len = MyList.map List.length myList

如果你发现自己经常使用某个特定的功能,你甚至可以给它自己的名字:

let myLen = MyList.map List.length
let len = myLen myList

Here是Wlaschin先生关于此主题的另一篇文章。

另请注意:这是方法不如功能的另一种方式。人们通常会尽可能避免使用方法。

答案 1 :(得分:3)

使用单一案例区分联合创建一种新类型 - 无论是技术还是逻辑意义 - 它还隐藏了该类型的实现细节。

如果要定义一个新类型(通过包装其他类型),没有理由为什么对包装类型list<int>起作用的操作也应该对类型MyList起作用(或者为什么它应该很容易让它们在MyList)上运行,因为您可以决定在任何时候更改MyList中使用的表示形式。

我认为列表的示例令人困惑 - 定义MyList没有太多实际意义(类型别名可能效果更好)。但是说你有类似Password的东西:

type Password = Password of string

现在,您不希望能够在string上运行任意Password操作,因为这可能会破坏您希望保留密码的约束(例如,它包含小写和大写字符)。如果您可以写pass.Substring(0, 1),那么您将创建无效密码!

所以,我认为如果你想隐藏底层表示,使用单例DU是有意义的。然后,您还需要定义适用于Password类型的操作,而不要使用string的任何操作。如果类型实际上相同(并且您的别名没有其他约束),则类型别名会更好。

答案 2 :(得分:2)

您可以这样做,它封装了您对内容的处理方式:

let length (MyList l) = l.Length
length myList