2011-06-19 3 views
6

В настоящее время я выполняю несколько базовых шаблонов, соответствующих котировкам.Оценка функции внутри котировки

Мой код:

let rec test e = 
    match e with 
    | Patterns.Lambda(v,e) -> test e 
    | Patterns.Call(_, mi, [P.Value(value, _); P.Value(value2, _)]) -> 
     printfn "Value1: %A | Value2 : %A" value value2 
    | Patterns.Call(_, mi, [P.Value(value, _); P.PropertyGet(_, pi, exprs)]) -> 
     printfn "Value1: %A | Value2 : %A" value (pi.GetValue(pi, null)) 
    | _ -> failwith "Expression not supported" 


let quot1 = <@ "Name" = "MyName" @> 
(* Call (None, Boolean op_Equality[String](System.String, System.String), 
     [Value ("Name"), Value ("lol")]) *) 

let quot2 = <@ "Name" = getNameById 5 @> 
(* Call (None, Boolean op_Equality[String](System.String, System.String), 
     [Value ("Name"), 
     Call (None, System.String getNameById[Int32](Int32), [Value (5)])]) *) 

test quot1 // Works! 
test quot2 // Fails.. Dosent match any of the patterns. 

Можно ли как-то оценить результат функции getNameById первого, так что он будет соответствовать одному из шаблонов, или я обречен назначить подведенный связывание с результатом функции вне цитаты?

Я пытался играть с ExprShape узорами, но без удачи ..

ответ

6

Вы можете использовать PowerPack-х Eval оценить только аргументы в Call выражения:

match e with 
| Call(_,mi,[arg1;arg2]) -> 
    let arg1Value, arg2Value = arg1.Eval(), arg2.Eval() 
    ... 

и аналогична для Lambda выражений и т.д. Заметил это освобождает вас от перечисляющих перестановок Value, Property и других выражений аргументов ,

Update

Так как вы хотите, чтобы избежать использования Eval (по уважительной причине, если вы реализуете производительности сознательное приложение), вам нужно реализовать свою собственную функцию Eval с помощью отражения (который до сих пор не осветления быстрый, но должен быть быстрее, чем у PowerPack's Eval, который включает в себя промежуточный перевод котировок F # на выражения Linq). Вы можете начать, поддерживая базовый набор выражений и расширяясь оттуда по мере необходимости. Рекурсия является ключевой, следующим могут помочь вам начать работу:

open Microsoft.FSharp.Quotations 
open System.Reflection 

let rec eval expr = 
    match expr with 
    | Patterns.Value(value,_) -> value //value 
    | Patterns.PropertyGet(Some(instance), pi, args) -> //instance property get 
     pi.GetValue(eval instance, evalAll args) //notice recursive eval of instance expression and arg expressions 
    | Patterns.PropertyGet(None, pi, args) -> //static property get 
     pi.GetValue(null, evalAll args) 
    | Patterns.Call(Some(instance), mi, args) -> //instance call 
     mi.Invoke(eval instance, evalAll args) 
    | Patterns.Call(None, mi, args) -> //static call 
     mi.Invoke(null, evalAll args) 
    | _ -> failwith "invalid expression" 
and evalAll exprs = 
    exprs |> Seq.map eval |> Seq.toArray 

А затем оборачивать это в Активном шаблоне улучшит синтаксис:

let (|Eval|) expr = 
    eval expr 

match e with 
| Patterns.Call(_, mi, [Eval(arg1Value); Eval(arg2Value)]) -> ... 

Update 2

OK, это нить заставила меня мотивироваться, чтобы попытаться реализовать надежное решение на основе отражения, и я сделал это с хорошими результатами, которые теперь являются частью Unquote с версии 2.0.0.

Это оказалось не таким сложным, как я думал, в настоящее время я поддерживаю все цитаты за исключением для AddressGet, AddressSet и NewDelegate. Это уже лучше, чем оценка PowerPack, которая не поддерживает PropertySet, VarSet, FieldSet, WhileLoop, ForIntegerRangeLoop и Quote, например.

Некоторые примечательные детали реализации с VarSet и VarGet, где мне нужно передать список поиска имени/переменной окружения для каждого рекурсивного вызова. Это действительно отличный пример красоты функционального программирования с неизменяемыми структурами данных.

Также следует обратить особое внимание на проблемы, связанные с исключениями: чередование объектов TargetInvokationException, создаваемых отражением, когда оно улавливает исключения, исходящие из методов, которые он вызывает (это очень важно для правильной оценки TryWith, а также для лучшей обработки пользователем исключения, которые вылетают из оценки котировок.

Возможно, самой «сложной» деталью реализации или действительно самой изнурительной была необходимость реализовать все основные операторы (ну, как я мог бы обнаружить: числовые и операторы преобразования, проверенные версии), так как большинство из них не получают динамических реализаций в библиотеке F # (они реализуются с использованием тестов статического типа без отложенных динамических реализаций), bu t также означает серьезное увеличение производительности при использовании этих функций.

Неформальный бенчмаркинг Я наблюдаю увеличение производительности до 50 раз по сравнению с PowerPack (не скомпилированным) eval.

Я также уверен, что мое решение на основе отражения будет меньше подвержено ошибкам, чем PowerPack, просто потому, что оно менее сложно, чем подход PowerPack (не говоря уже о том, что я поддержал его примерно с 150 модульными тестами, должным образом укрепленными Unquotes дополнительных 200+ модульных тестов, которые теперь обусловлены этой реализацией eval).

Если вы хотите заглянуть в исходный код, основными модулями являются Evaluation.fs и DynamicOperators.fs (я заблокировал ссылки в ревизии 257). Не стесняйтесь брать и использовать исходный код для своих целей, он лицензируется в соответствии с Apache License 2.0! Или вы можете подождать неделю или около того, когда я выпущу Unquote 2.0.0, который будет публиковаться операторами оценки и расширениями.

+0

@Stephen, да, это сработает .. но мне все еще интересно, повлияет ли это на производительность моего приложения с тех пор Я слышал немало неприятностей о функции '.Eval()' и ее производительности. – ebb

+0

Я использую 'Eval' много в Unquote и да, это не так быстро, как могло бы быть, но это, безусловно, достаточно для работы Unquote. Но для вашего проекта, перевод выражений F # в запросы базы данных, я был бы осторожен. –

+0

@ebb - Я обновил свой ответ с помощью основанной на отражении функции eval, которая должна работать лучше, чем Power Evolution, и может быть достаточной для ваших нужд. –

1

Вы можете написать интерпретатор, который будет оценивать котировку и вызова getNameById функции с использованием Reflection. Однако это будет довольно много работы. ExprShape не поможет вам - это полезно для простого прохождения цитат, но для написания интерпретатора вам нужно будет охватить все шаблоны.

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

#r "FSharp.PowerPack.Linq.dll" 

open Microsoft.FSharp.Linq.QuotationEvaluation 

let getNameById n = 
    if n = 5 then "Name" else "Foo" 

let quot1 = <@ "Name" = "MyName" @> 
let quot2 = <@ "Name" = getNameById 5 @> 

quot1.Eval()  
quot2.Eval()  

Это имеет некоторые ограничения, но это действительно самый простой вариант. Однако я не совсем уверен, чего вы пытаетесь достичь. Если бы вы могли это прояснить, тогда вы можете получить лучший ответ.

+0

Я пишу простой переводчик цитат для mongodb. Пример: вы передаете '<@ fun z -> z.Name = getNameById 5 @>', и он будет сопоставляться с некоторыми шаблонами, чтобы создать допустимый запрос mongo, используя свои собственные функции запросов. Доцент «.Eval()» действительно помогает, поскольку он вычисляет всю цитату, а не только функцию getNameById внутри. – ebb

+0

@ebb О, я вижу. И как вы хотите перевести вызов функции? –

+0

Функция 'getNameById'? хе-хе .. да, я подумал об этом в течение нескольких дней, и я все еще пуст. Моя единственная идея состояла в том, чтобы каким-то образом оценить функцию перед передачей цитаты в соответствие шаблону, но это кажется невозможным. – ebb

Смежные вопросы