F# 除了从函数返回受歧视的并集,还有什么其他选择?
我正在尝试用F#重写一段复杂的代码。 对于这个特殊的代码库,有差别的联合对我帮助很大,所以我将尽可能多地使用它们。具体来说,DU的穷尽性检查帮助我避免了许许多多的bug 然而,我面临着一个重复的模式,即必须使用F# 除了从函数返回受歧视的并集,还有什么其他选择?,f#,pattern-matching,discriminated-union,F#,Pattern Matching,Discriminated Union,我正在尝试用F#重写一段复杂的代码。 对于这个特殊的代码库,有差别的联合对我帮助很大,所以我将尽可能多地使用它们。具体来说,DU的穷尽性检查帮助我避免了许许多多的bug 然而,我面临着一个重复的模式,即必须使用match。。。在某种程度上,代码中的混乱抵消了我从穷尽性检查中获得的好处。 我尽可能地简化了我正在处理的模式,并试图给出一个示例来演示我正在编写的代码的结构。真正的代码库要复杂得多,它位于一个完全不同的领域,但在语言级别上,这个示例代表了这个问题 假设我们想根据购物者的分类来了解购物者:
match。。。在某种程度上,代码中的混乱抵消了我从穷尽性检查中获得的好处。
我尽可能地简化了我正在处理的模式,并试图给出一个示例来演示我正在编写的代码的结构。真正的代码库要复杂得多,它位于一个完全不同的领域,但在语言级别上,这个示例代表了这个问题
假设我们想根据购物者的分类来了解购物者:他们要么是猫族,要么是狗族。这里的关键是通过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
或dogfooddshopper
此时,我最初的想法是定义一个购物者类型,因为我无法根据模式匹配的结果从以下函数返回两种不同的类型:
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
,需要根据PetPerson
值得到一个CatFoodShopper
或dogfooddshopper
。这导致了不必要的模式匹配的情况下,我知道我没有在手上。以下是一个例子:
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
当使用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"
请注意,这个示例旨在演示使用DU和使用接口返回基于分类DU参数的不同类型之间的权衡,如果我可以这样称呼它的话。所以不要挂断我对返回值等的毫无意义的使用
我的问题是:有没有其他方法可以实现对一组元组(或记录)类型进行分类的语义?如果您考虑的是活动模式,那么它们不是一个选项,因为在真正的代码库中,DU有七种以上的情况,这是活动模式的限制,以防它们会有所帮助。那么,对于上述方法,我还有其他改进方法吗 一个明显的方法是在匹配PetPerson
之前打电话给ShopperViaPersonality
,而不是在匹配PetPerson
之后:
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+a函数几乎相同,但在语法层面上,请注意嵌套现在少了很多。谢谢Fyodor。首先调用ShopperViaPersonality是正确的,但不幸的是,在原始代码库中不可能这样做。这是一个过于简单的例子,重点是语言层面的权衡。如果它们不限于七种,我会使用它们。事实上,我在问题的末尾写了这个。原始代码在DUs中有七种以上的可能情况。我会考虑“不,你没有任何其他选择比这些”是一个有效的回答我的问题。我基本上是想确定在这种情况下,F#可以做什么。为什么在原始代码库中不可能在匹配之前调用函数?你的例子没有显示这一点,你也没有在文本中提及。我实际上试图在文本中提及这一点,但很抱歉,如果我没有说清楚的话。基本上,整个要点是使用DU对元组的编译时分类进行编码。逻辑由分类DU驱动(以PetPerson为例),调用的上下文要复杂得多。很难将问题归结为一个简单的例子,但这是我的设置:)好吧,如果你不描述问题,恐怕没有人能提供解决方案。从逻辑上考虑,如果有一种方法可以从分类中得到一个有区别的元组,那么为什么不使用它来代替分类本身呢。如果获取不同元组的方法不同,需要不同的上下文,那么它们肯定应该通过不同的函数而不是单一的ShopperViaPersonality
?如果你认真描述你的问题,我相信社区会想出一个解决方案。我的答案解决了你的问题,如原帖子所述。