我在优秀的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时,是否有一种简单的方法可以访问“别名”或“已查看”类型的方法?
答案 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