Цитаты F#: переменная может выходить за рамки

У меня есть немного кода:

let rec h n z = if n = 0 then z
                else <@ (fun x -> %(h (n - 1) <@ x + %z @>)) n @>

преобразован из примера MetaOcaml в http://www.cs.rice.edu/~taha/publications/journal/dspg04a.pdf

В статье объясняется, что приведенный выше пример даст следующее с параметрами 3 а также .<1>. (в обозначении MetaOcaml):

.<(fun x_1 -> (fun x_2 -> (fun x_3 -> x_3 + (x_2 + (x_1 + 1))) 1) 2) 3>.

Как вы можете видеть xЗаменяется x_1, x_2 и т. д. потому что x в противном случае будет относиться только к x в глубине души fun,

Но в F# это не разрешено. Я получаю ошибку во время компиляции: "Переменная 'x' связана в кавычке, но используется как часть объединенного выражения. Это недопустимо, поскольку она может выходить за пределы своей области". Итак, вопрос: как это можно изменить, чтобы он компилировался и имел ту же семантику, что и выходные данные MetaOcaml?

Обновление для комментария: я использую PowerPack для фактической оценки цитаты. Но я не думаю, что это имеет какое-либо отношение к этому, потому что ошибка во время компиляции. Пока что QuotationEvaluation работает. Однако я знаю, что это может быть не самая эффективная реализация.

Обновление до ответа Томаса: я действительно не хочу x быть глобальным или выйти за рамки. Но я хочу это эквивалентно

let rec h n z = if n = 0 then z
                else (fun x -> (h (n - 1) (x + z))) n

с цитатами. Ваш ответ дает (h 3 <@ 1 @>).Eval() = 4 где вышесказанное дает h 3 1 = 7, И здесь я хочу 7 быть ответом.

1 ответ

Решение

Синтаксис цитаты F# не поддерживает переменные, которые потенциально могут выходить за рамки видимости, поэтому вам нужно явно построить дерево, используя Expr операции. Нечто подобное должно сработать:

open Microsoft.FSharp.Quotations

let rec h n (z:Expr<int>) = 
  if n = 0 then z                
  else 
    let v = new Var("x", typeof<int>)
    let ve = Expr.Var(v)
    Expr.Cast<int>
        (Expr.Application( Expr.Lambda(v, h (n - 1) <@ %%ve + %z @>), 
                           Expr.Value(n)))

Однако это довольно искусственный пример (для демонстрации захвата переменных в MetaOCaml, который недоступен в F#). Он просто генерирует выражение как (2 + (1 + ...)), Вы можете получить тот же результат, написав что-то вроде этого:

let rec h n (z:Expr<int>) = 
  if n = 0 then z                
  else h (n - 1) <@ n + %z @>

Или даже лучше:

[ 1 .. 4 ] |> List.fold (fun st n -> <@ n + %st @>) <@ 0 @>

Я также столкнулся с этим ограничением в цитатах F#, и было бы хорошо, если бы это было поддержано. Однако я не думаю, что это такая большая проблема на практике, потому что цитаты F# не используются для поэтапного метапрограммирования. Они более полезны для анализа существующего кода F#, чем для генерации кода.

Другие вопросы по тегам