F# 为什么可以';我是否可以通过一个歧视联盟的成员列表来简化这个迭代?
通常,人们希望通过异构对象(不同类型)的集合进行迭代(使用map、iter或fold)。处理这个问题的一种方法是创建一个有区别的并集,它允许创建一个列表,其中的对象被适当地转换为DU案例。下面的代码在一个简单的示例中实现了这一点:F# 为什么可以';我是否可以通过一个歧视联盟的成员列表来简化这个迭代?,f#,discriminated-union,F#,Discriminated Union,通常,人们希望通过异构对象(不同类型)的集合进行迭代(使用map、iter或fold)。处理这个问题的一种方法是创建一个有区别的并集,它允许创建一个列表,其中的对象被适当地转换为DU案例。下面的代码在一个简单的示例中实现了这一点: type MYDU = | X1 of int | X2 of float | X3 of string let bar (y: MYDU) = match y with | X1 x -> pr
type MYDU = | X1 of int
| X2 of float
| X3 of string
let bar (y: MYDU) =
match y with
| X1 x -> printfn "%A" x
| X2 x -> printfn "%A" x
| X3 x -> printfn "%A" x
[X1(1); X2(2.0); X3("3"); X1(4)]
|> List.map bar |> ignore
这段代码运行良好,可以打印
1
2.0
"3"
4
太好了!但我想知道是否可以避免重复调用printfn
。我尝试了以下操作,但无法编译:
let baz (y: MYDU) =
match y with
| X1 x | X2 x | X3 x -> printfn "%A" x // red squiggly line under X1 x
编译器发出以下消息:
此表达式应具有类型“int”,但此处具有类型“float”
我怀疑避免重复是可行的,但我肯定犯了一个基本的错误。有什么建议吗?你没有犯错误,这是F#的类型系统所不允许的 在匹配大小写箭头的左侧可以有多个图案,但它们必须绑定同一组值(包括类型)。这里,
x
对于每个模式都有不同的类型,这足以让编译器抱怨
有一些方法可以减轻痛苦(您可以在DU上有一个成员返回装箱值,或者您可以有一个活动模式在比赛中装箱),但它们是高度情景化的。将模式拆分为单独的案例,并在真空中为每一个案例重复右侧始终是一个更好的解决方案。您可以改为将参数转换为通用类型以进行打印,然后打印该值。您仍然可以获得模式匹配和有区别的联合的优势:) 下面是这种方法的一个例子
type MYDU =
| X1 of int
| X2 of float
| X3 of string
let bar y =
let myStr =
match y with
| X1 x -> string x
| X2 x -> string x
| X3 x -> x
printfn "%s" myStr
bar (X1 5)
通过将值“装箱”到
obj
类型,可以在一定程度上避免这种重复。这是.NET的别名:“.NET Framework中所有类的最终基类”。这意味着任何类型的任何值也是一个obj
但是,当您框选对象时,将丢失静态键入。您正在颠覆F#type系统并增加出错的可能性。这就是为什么通常应该避免,除非你有充分的理由这样做
函数printfn“%A”
可以采用任何类型,因此其类型签名有效地是obj->unit
。如果您只想对值运行此函数,那么使用装箱可能是合理的。您可以定义此活动模式,它使用框
功能:
let (|Box|) x = box x
然后使用如下模式:
let printMyDu myDu =
match myDu with
| X1 (Box x)
| X2 (Box x)
| X3 (Box x) -> printfn "%A" x
同样,如果可能的话,您应该避免这样做,因为在许多情况下您正在失去类型安全性。例如,如果您只是在稍后检查值的类型时才选中一个值,那么您可能对F#采取了错误的方法。在本例中,我们将值
x
放入框中,然后立即使用并丢弃它,这样我们就不会降低总体类型安全性。从示例代码片段中很难判断,但是在实践中,具有真正异构列表的场景应该非常罕见,这样的事情就不会麻烦了。在CodeReview中可能值得一提的是,您可能以一种太低级的方式对域进行建模。一般来说,无论何时您在执行List.map f |>ignore
,您都应该使用List.iter
,尤其是如果f
是一个返回单位的函数(就像您的bar
函数那样). 如果您计划切换到使用Seq.map
而不是List.map
,这一点尤其重要,因为Seq.map
是惰性的,在必须之前不会迭代整个Seq,但是Seq.iter
将按照您的预期迭代整个Seq。因此,使用List.iter
而不是List.map
将保护您在切换到使用Seq
时免受意外。我认为在一般情况下,转换为普通类型是不可能的。中的kvb
的(接受的)答案似乎正是我想做的。问题的标题是“在联合类型的F#列表上操作”,所以我仍然感到困惑。@Soldalma:在这个答案中,他绑定的值(s
和l
)在匹配大小写的两种模式中都具有相同的类型(string
和Section List
)。这里不是这种情况-x
在每个模式中都有不同的类型。仅此一点就足以使编译失败,即使匹配的右侧分别与这些值中的每一个“起作用”。谢谢