Может ли компилятор F# использовать каррирование для разделения путей кода в зависимости от типа?

Может ли компилятор F# отделить пути кода, каррируя функцию, в которой разные типы подразумевают разные пути через последующие вызываемые функции?

Рассмотрим следующий размеченный союз. Есть 2 возможности, которые теоретически относятся к разным типам:

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)

Таким образом, Halve и Double являются отдельными путями кода, и мы могли бы написать их как две отдельные функции.

Теоретически каррирование означает наличие двух разных функций; если вы присвоите первому параметру любой из типов Choice.Halve или Choice.Double (как в doubler), то у вас будет функция, специфичная для этого типа, и компилятор сможет оптимизировать более поздние ответвления.

Так ли это?

Если я посмотрю в IL, я не увижу такой оптимизации, но я предполагаю, что это возможно JITted. Коллега предполагает, что прогнозирование ветвлений делает такую ​​оптимизацию ненужной.

Единственный способ избежать ненужных ветвей, чтобы инвертировать структуру и передать функцию divide/halve?

-- Редактировать --

Джон Палмер предложил добавить 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

Однако очевидной оптимизации каррированной функции doubler в main нет, поэтому оптимизируется некаррированная функция, а не каррированная.


person Phil H    schedule 06.06.2014    source источник
comment
Вы пытались использовать inline в какой-либо из функций?   -  person John Palmer    schedule 06.06.2014


Ответы (1)


Я не думаю, что компилятор сделает это автоматически, но вы можете написать свой код так, чтобы он реализовал это явно:

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

Эту функцию можно использовать так же, как и исходную функцию, например. inner 1.0 2.0, но когда вы вызываете его только с одним аргументом, он фактически запускает первую часть тела и возвращает функцию, созданную в теле случая совпадения.

person Tomas Petricek    schedule 06.06.2014
comment
И обратите внимание, что (fun value -> halve value) — это просто расширенный способ написания halve. - person kvb; 06.06.2014
comment
Итак, если я заменю halve value на halve, divide value на divide и удалю значение из списка аргументов inner, это обеспечит отдельный путь и будет оптимизировано? Почему такая разница? - person Phil H; 06.06.2014