f#编译器是否可以通过套用一个函数来分离代码路径,其中不同的类型意味着通过随后调用的函数的不同路径?
考虑下面的歧视联合。有两种可能,理论上是不同的类型:
type Choice =
| Halve
| Double
假设我们有一些特定的函数用于这些情况:
let halve value = value / 2.0
let divide value = value * 2.0
和2个函数,根据某些param
的类型提供2个单独的代码路径(其余部分用于完成fs文件):
let inner param value =
match param with
| Choice.Halve -> halve value
| Choice.Double -> divide value
let outer param value =
let internalVariable =
match param with
| Choice.Halve -> "halving"
| Choice.Double -> "doubling"
inner param value
[<EntryPoint>]
let main argv =
printf "%gn" (outer Choice.Halve 4.0)
let doubler = outer Choice.Double
printf "%gn" (doubler 6.0)
half和Double因此是独立的代码路径,我们可以把它们写成两个独立的函数。
理论上,柯里化会说有两个不同的函数;如果您将第一个参数curry到选择中的任何一个。一半或选择。双类型(如doubler
),那么你有一个特定于该类型的函数,编译器应该能够优化以后的分支。
是这样吗?
如果我在IL中查看,我可以看到没有这样的优化,但我想这可能是jit。一位同事认为,分支预测使这种优化变得不必要。
唯一避免不必要的分支的方法是将结构倒置并将divide
/halve
函数传递进去吗?
—Edit—
John Palmer建议添加inline
,所以我尝试了一下,并为outer
获得了以下优化的IL:
IL_0001: ldarg.0
IL_0002: call instance int32 Program/Choice::get_Tag()
IL_0007: ldc.i4.1
IL_0008: bne.un.s IL_0016
IL_000a: ldarg.1
IL_000b: ldc.r8 2
IL_0014: mul
IL_0015: ret
IL_0016: ldarg.1
IL_0017: ldc.r8 2
IL_0020: div
IL_0021: ret
然而,在main
中没有明显的优化doubler
的柯里化函数——所以未柯里化的函数被优化了,而不是柯里化的函数。
我不认为编译器会自动做到这一点,但你可以写你的代码,使它显式地实现:
let inner param =
match param with
| Choice.Halve -> (fun value -> halve value)
| Choice.Double -> (fun value -> divide value)
这个函数可以像你的原始函数一样使用,例如inner 1.0 2.0
,但是当你只使用一个参数调用它时,它实际上运行body的第一部分,并返回一个在match情况下创建的函数。