从函数返回可区分联合的替代方案是什么?
What are the alternatives to returning a discriminated union from a function?
我正在尝试使用 F# 重写一段复杂的代码。
对于这个特定的代码库,discriminated union 帮了我很多,所以我专注于尽可能多地使用它们。具体来说,对 DU 的详尽检查帮助我避免了很多错误。
但是,我面临着不得不使用 match ... with
的重复模式,以至于代码中的混乱抵消了我从详尽检查中获得的好处。
我尽可能地简化了我正在处理的模式,并试图提出一个示例来演示我正在编写的代码的结构。真正的代码库要复杂得多,它在一个完全不同的领域,但在语言层面,这个例子代表了问题。
假设我们想根据购物者的分类获得一些关于购物者的信息:他们要么是猫人,要么是狗人。这里的关键是通过 DU 对某些类型(元组)进行分类。
以下是域类型:
type PetPerson =
|CatPerson
|DogPerson
type CatFood =
|Chicken
|Fish
type DogFood =
|Burger
|Steak
//some cat food, shopper's age and address
type CatFoodShopper = CatFoodShopper of (CatFood list * int * string)
//some dog food, shopper's age and number of children
type DogFoodShopper = DogFoodShopper of (DogFood list * int * int)
撇开我们喂养可怜的动物的可怕方式不谈,这个领域模型需要一个函数来将 PetPerson
映射到 CatFoodShopper
或 DogFoodShopper
在这一点上,我最初的想法是定义一个 Shopper 类型,因为我不能根据模式匹配的结果 return 来自以下函数的两种不同类型:
type Shopper =
|CatFShopper of CatFoodShopper
|DogFShopper of DogFoodShopper
let ShopperViaPersonality = function
|CatPerson -> CatFShopper (CatFoodShopper ([Chicken;Fish], 32, "Hope St"))
|DogPerson -> DogFShopper (DogFoodShopper ([Burger;Steak], 45, 1))
这解决了问题,但我在代码中有很多地方(真的很多),我最终得到一个 PetPerson
并且需要得到一个 CatFoodShopper
或 DogFoodShopper
基于 PetPerson
的值。对于我知道我手头没有的情况,这会导致不必要的模式匹配。这是一个例子:
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match y with
|CatPerson as c -> //how can I void the following match?
match (ShopperViaPersonality c) with
|CatFShopper (CatFoodShopper (lst,_,_))-> "use lst and return some string "
| _ -> failwith "should not have anything but CatFShopper"
|DogPerson as d -> //same as before. I know I'll get back DogFShopper
match (ShopperViaPersonality d) with
|DogFShopper (DogFoodShopper (lst, _,_)) -> "use lst and return other string"
|_ -> failwith "should not have anything but DogFShopper"
如您所见,我必须编写模式匹配代码,即使我知道我将取回特定值。我无法简明地将 CatPerson
值与 CatFoodShopper
值相关联。
为了改进调用站点的内容,我考虑使用 F# 通过接口模仿类型的方式 类,基于此处提供的大量示例:
type IShopperViaPersonality<'T> =
abstract member ShopperOf: PetPerson -> 'T
let mappingInstanceOf<'T> (inst:IShopperViaPersonality<'T>) p = inst.ShopperOf p
let CatPersonShopper =
{new IShopperViaPersonality<_> with
member this.ShopperOf x =
match x with
|CatPerson -> CatFoodShopper ([Chicken;Fish], 32, "Hope St")
| _ -> failwith "This implementation is only for CatPerson"}
let CatPersonToShopper = mappingInstanceOf CatPersonShopper
let DogPersonShopper =
{new IShopperViaPersonality<_> with
member this.ShopperOf x =
match x with
|DogPerson -> DogFoodShopper ([Burger;Steak], 45, 1)
| _ -> failwith "This implementation is only for DogPerson"}
let DogPersonToShopper = mappingInstanceOf DogPersonShopper
所以我不再使用 Shopper
类型来表示猫食购物者和狗食购物者,而是一个接口定义了从 PetPerson
值到特定购物者类型的映射。我也有单独的部分应用功能,使呼叫站点的事情变得更加容易。
let UsePersonality1 (x:int) (y:PetPerson) =
match y with
|CatPerson as c ->
let (CatFoodShopper (lst,_,_)) = CatPersonToShopper c
"use lst and return string"
|DogPerson as d ->
let (DogFoodShopper (lst,_,_)) = DogPersonToShopper d
"use lst and return string"
这种方法在使用 PetPerson 值时效果更好,但我现在的任务是定义这些单独的函数以保持调用站点的整洁。
请注意,此示例旨在演示使用 DU 和使用基于分类 DU 参数的 return 不同类型的接口之间的权衡,如果我可以这样称呼的话。所以不要挂断我对 return 值等毫无意义的使用
我的问题是:有没有其他方法可以完成对一堆元组(或记录)类型进行分类的语义?如果您正在考虑活动模式,则它们不是一个选项,因为在实际代码库中,DU 有超过七个案例,这是活动模式的限制,以防它们有所帮助。那么我还有其他选择可以改进上述方法吗?
解决此问题的一个明显方法是在 PetPerson
匹配 PetPerson
之前调用 ShopperViaPersonality
,而不是在
之后调用
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match ShopperViaPersonality y with
| CatFShopper (CatFoodShopper (lst,_,_))-> "use lst and return some string "
| DogFShopper (DogFoodShopper (lst, _,_)) -> "use lst and return other string"
另请注意,如果 ShooperViaPersonality
的唯一目的是支持模式匹配,则最好将其设为活动模式:
let (|CatFShopper|DogFShopper|) = function
| CatPerson -> CatFShopper ([Chicken;Fish], 32, "Hope St")
| DogPerson -> DogFShopper ([Burger;Steak], 45, 1)
那么你可以这样使用它:
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match y with
| CatFShopper (lst,_,_) -> "use lst and return some string "
| DogFShopper (lst, _,_) -> "use lst and return other string"
从逻辑上讲,活动模式与 DU + 函数几乎相同,但在语法级别上,请注意现在嵌套少了多少。
我正在尝试使用 F# 重写一段复杂的代码。 对于这个特定的代码库,discriminated union 帮了我很多,所以我专注于尽可能多地使用它们。具体来说,对 DU 的详尽检查帮助我避免了很多错误。
但是,我面临着不得不使用 match ... with
的重复模式,以至于代码中的混乱抵消了我从详尽检查中获得的好处。
我尽可能地简化了我正在处理的模式,并试图提出一个示例来演示我正在编写的代码的结构。真正的代码库要复杂得多,它在一个完全不同的领域,但在语言层面,这个例子代表了问题。
假设我们想根据购物者的分类获得一些关于购物者的信息:他们要么是猫人,要么是狗人。这里的关键是通过 DU 对某些类型(元组)进行分类。 以下是域类型:
type PetPerson =
|CatPerson
|DogPerson
type CatFood =
|Chicken
|Fish
type DogFood =
|Burger
|Steak
//some cat food, shopper's age and address
type CatFoodShopper = CatFoodShopper of (CatFood list * int * string)
//some dog food, shopper's age and number of children
type DogFoodShopper = DogFoodShopper of (DogFood list * int * int)
撇开我们喂养可怜的动物的可怕方式不谈,这个领域模型需要一个函数来将 PetPerson
映射到 CatFoodShopper
或 DogFoodShopper
在这一点上,我最初的想法是定义一个 Shopper 类型,因为我不能根据模式匹配的结果 return 来自以下函数的两种不同类型:
type Shopper =
|CatFShopper of CatFoodShopper
|DogFShopper of DogFoodShopper
let ShopperViaPersonality = function
|CatPerson -> CatFShopper (CatFoodShopper ([Chicken;Fish], 32, "Hope St"))
|DogPerson -> DogFShopper (DogFoodShopper ([Burger;Steak], 45, 1))
这解决了问题,但我在代码中有很多地方(真的很多),我最终得到一个 PetPerson
并且需要得到一个 CatFoodShopper
或 DogFoodShopper
基于 PetPerson
的值。对于我知道我手头没有的情况,这会导致不必要的模式匹配。这是一个例子:
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match y with
|CatPerson as c -> //how can I void the following match?
match (ShopperViaPersonality c) with
|CatFShopper (CatFoodShopper (lst,_,_))-> "use lst and return some string "
| _ -> failwith "should not have anything but CatFShopper"
|DogPerson as d -> //same as before. I know I'll get back DogFShopper
match (ShopperViaPersonality d) with
|DogFShopper (DogFoodShopper (lst, _,_)) -> "use lst and return other string"
|_ -> failwith "should not have anything but DogFShopper"
如您所见,我必须编写模式匹配代码,即使我知道我将取回特定值。我无法简明地将 CatPerson
值与 CatFoodShopper
值相关联。
为了改进调用站点的内容,我考虑使用 F# 通过接口模仿类型的方式 类,基于此处提供的大量示例:
type IShopperViaPersonality<'T> =
abstract member ShopperOf: PetPerson -> 'T
let mappingInstanceOf<'T> (inst:IShopperViaPersonality<'T>) p = inst.ShopperOf p
let CatPersonShopper =
{new IShopperViaPersonality<_> with
member this.ShopperOf x =
match x with
|CatPerson -> CatFoodShopper ([Chicken;Fish], 32, "Hope St")
| _ -> failwith "This implementation is only for CatPerson"}
let CatPersonToShopper = mappingInstanceOf CatPersonShopper
let DogPersonShopper =
{new IShopperViaPersonality<_> with
member this.ShopperOf x =
match x with
|DogPerson -> DogFoodShopper ([Burger;Steak], 45, 1)
| _ -> failwith "This implementation is only for DogPerson"}
let DogPersonToShopper = mappingInstanceOf DogPersonShopper
所以我不再使用 Shopper
类型来表示猫食购物者和狗食购物者,而是一个接口定义了从 PetPerson
值到特定购物者类型的映射。我也有单独的部分应用功能,使呼叫站点的事情变得更加容易。
let UsePersonality1 (x:int) (y:PetPerson) =
match y with
|CatPerson as c ->
let (CatFoodShopper (lst,_,_)) = CatPersonToShopper c
"use lst and return string"
|DogPerson as d ->
let (DogFoodShopper (lst,_,_)) = DogPersonToShopper d
"use lst and return string"
这种方法在使用 PetPerson 值时效果更好,但我现在的任务是定义这些单独的函数以保持调用站点的整洁。
请注意,此示例旨在演示使用 DU 和使用基于分类 DU 参数的 return 不同类型的接口之间的权衡,如果我可以这样称呼的话。所以不要挂断我对 return 值等毫无意义的使用
我的问题是:有没有其他方法可以完成对一堆元组(或记录)类型进行分类的语义?如果您正在考虑活动模式,则它们不是一个选项,因为在实际代码库中,DU 有超过七个案例,这是活动模式的限制,以防它们有所帮助。那么我还有其他选择可以改进上述方法吗?
解决此问题的一个明显方法是在 PetPerson
匹配 PetPerson
之前调用 ShopperViaPersonality
,而不是在
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match ShopperViaPersonality y with
| CatFShopper (CatFoodShopper (lst,_,_))-> "use lst and return some string "
| DogFShopper (DogFoodShopper (lst, _,_)) -> "use lst and return other string"
另请注意,如果 ShooperViaPersonality
的唯一目的是支持模式匹配,则最好将其设为活动模式:
let (|CatFShopper|DogFShopper|) = function
| CatPerson -> CatFShopper ([Chicken;Fish], 32, "Hope St")
| DogPerson -> DogFShopper ([Burger;Steak], 45, 1)
那么你可以这样使用它:
let UsePersonality (x:int) (y:PetPerson) =
//x is used in some way etc. etc.
match y with
| CatFShopper (lst,_,_) -> "use lst and return some string "
| DogFShopper (lst, _,_) -> "use lst and return other string"
从逻辑上讲,活动模式与 DU + 函数几乎相同,但在语法级别上,请注意现在嵌套少了多少。