我可以在单例区分联合中调用 "aliased" 类型的方法吗?
Can I call methods of "aliased" types in single-case discriminated unions?
我在优秀的 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 时,是否有一种简单的方法来访问 "aliased" 或 "viewed" 类型的方法?
你可以这样做,它封装了你对内容所做的事情:
let length (MyList l) = l.Length
length myList
这不是你所说的"alias"。您已经声明了一个全新的类型,其中包含一个列表。您的类型不仅仅是列表的另一个名称,它是一个包装器。
如果你想声明一个真正的别名,你应该使用语法type A = B
,例如:
type MyList = List<int>
let myList : MyList = [1; 2; 3]
printf "Length is %d" myList.Length
有了这样的别名,MyList
将 100% 替代 List<int>
,反之亦然。这就是通常所说的"alias"。同一事物的不同名称。
另一方面,包装器将为您提供额外的类型安全性:如果您尝试在预期 MyList
的地方使用裸 List<int>
,编译器将捕获。这就是通常使用此类包装器的方式。但是这种额外的保护是有代价的:你不能把 MyList
当作一个列表来使用。因为那会扼杀整个想法。不能两全其美。
问:等等,你是说我必须重新实现所有列表功能才能获得这个包装器吗?
嗯,不。如果你从方法转向函数,你可以通过提供 map
:
来使这个 wrapping/unwrapping 通用
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 先生关于该主题的另一篇文章。
另请注意:这是方法不如函数的另一种方式。人们通常会尽可能避免使用方法。
使用单例区分联合创建了一个新类型——无论是在技术上还是逻辑上——而且它还隐藏了该类型的实现细节。
如果您正在定义一个新类型(通过包装其他类型),则没有理由对包装类型 list<int>
起作用的操作也应该对类型 MyList
起作用(或者为什么让它们在 MyList
上工作应该很容易),因为您可以决定随时更改 MyList
中使用的表示。
我认为带有列表的示例令人困惑 - 定义 MyList
没有太多实际用途(并且类型别名可能会更好)。但是假设你有类似 Password
:
type Password = Password of string
现在,您不希望能够对 Password
进行 运行 任意 string
操作,因为这可能会破坏您希望持有的有关密码的限制(例如,它包含小写和大写字符的混合)。如果您可以写 pass.Substring(0, 1)
,您将创建无效密码!
因此,如果您想隐藏底层表示,我认为使用单例 DU 是有意义的。然后您还想定义适用于类型 Password
的操作,并且不要使用 string
的任何操作。如果类型确实相同(并且您的别名没有其他限制),则类型别名会更好。
我在优秀的 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 时,是否有一种简单的方法来访问 "aliased" 或 "viewed" 类型的方法?
你可以这样做,它封装了你对内容所做的事情:
let length (MyList l) = l.Length
length myList
这不是你所说的"alias"。您已经声明了一个全新的类型,其中包含一个列表。您的类型不仅仅是列表的另一个名称,它是一个包装器。
如果你想声明一个真正的别名,你应该使用语法type A = B
,例如:
type MyList = List<int>
let myList : MyList = [1; 2; 3]
printf "Length is %d" myList.Length
有了这样的别名,MyList
将 100% 替代 List<int>
,反之亦然。这就是通常所说的"alias"。同一事物的不同名称。
另一方面,包装器将为您提供额外的类型安全性:如果您尝试在预期 MyList
的地方使用裸 List<int>
,编译器将捕获。这就是通常使用此类包装器的方式。但是这种额外的保护是有代价的:你不能把 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 先生关于该主题的另一篇文章。
另请注意:这是方法不如函数的另一种方式。人们通常会尽可能避免使用方法。
使用单例区分联合创建了一个新类型——无论是在技术上还是逻辑上——而且它还隐藏了该类型的实现细节。
如果您正在定义一个新类型(通过包装其他类型),则没有理由对包装类型 list<int>
起作用的操作也应该对类型 MyList
起作用(或者为什么让它们在 MyList
上工作应该很容易),因为您可以决定随时更改 MyList
中使用的表示。
我认为带有列表的示例令人困惑 - 定义 MyList
没有太多实际用途(并且类型别名可能会更好)。但是假设你有类似 Password
:
type Password = Password of string
现在,您不希望能够对 Password
进行 运行 任意 string
操作,因为这可能会破坏您希望持有的有关密码的限制(例如,它包含小写和大写字符的混合)。如果您可以写 pass.Substring(0, 1)
,您将创建无效密码!
因此,如果您想隐藏底层表示,我认为使用单例 DU 是有意义的。然后您还想定义适用于类型 Password
的操作,并且不要使用 string
的任何操作。如果类型确实相同(并且您的别名没有其他限制),则类型别名会更好。