Цитаты 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#, чем для генерации кода.