0

F#编译器可以通过currying一个函数来分离代码路径,其中不同类型意味着通过后续调用的函数的不同路径?

考虑以下有区别的联合。有两种可能性,理论上是不同的类型:

type Choice =
    | Halve
    | Double

假设我们对其中一些情况有一些特定的功能:

let halve value = value / 2.0
let divide value = value * 2.0

以及 2 个函数,它们根据某些类型提供 2 个单独的代码路径param(完成 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 "%g\n" (outer Choice.Halve 4.0)
    let doubler = outer Choice.Double
    printf "%g\n" (doubler 6.0)

因此,Half 和 Double 是独立的代码路径,我们可以将它们编写为两个独立的函数。

从理论上讲,柯里化会说有两种不同的功能;如果您将第一个参数 curry 为 Choice.Halve 或 Choice.Double 类型(如 in doubler),那么您拥有特定于该类型的函数,编译器应该能够优化以后的分支。

是这样吗?

如果我查看 IL,我看不到这样的优化,但我想这可能是 JITted。一位同事建议分支预测使这种优化变得不必要。

是避免不必要的分支反转结构并传递divide/halve函数的唯一方法吗?

- 编辑 -

John Palmer 建议添加inline,所以我尝试了一下,得到了以下优化的 IL outer

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

然而,curried 函数并没有明显的优化- 所以 uncurrieddoubler函数main正在被优化,而不是 curried 函数。

4

1 回答 1

2

我不认为编译器会自动执行此操作,但您可以编写代码,使其显式实现:

let inner param = 
    match param with
    | Choice.Halve -> (fun value -> halve value)
    | Choice.Double -> (fun value -> divide value)

这个函数可以像你原来的函数一样使用,例如inner 1.0 2.0,但是当你只用一个参数调用它时,它实际上运行了主体的第一部分并返回一个在匹配案例主体中创建的函数。

于 2014-06-06T10:04:23.137 回答