Оценить функцию внутри цитаты - PullRequest
6 голосов
/ 19 июня 2011

В данный момент я делаю очень простое сопоставление с шаблоном и цитатами.

Мой код:

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во-первых, чтобы он соответствовал одному из шаблонов, или я обречен назначить привязку let с результатом функции вне цитаты?

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

Ответы [ 2 ]

7 голосов
/ 19 июня 2011

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

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

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

Обновление

Поскольку вы хотите избегать использования Eval (по уважительной причине, если вы реализуете производительностьсознательное приложение), вам нужно будет реализовать свою собственную функцию eval с использованием отражения (которая все еще не очень быстрая, но должна быть быстрее, чем Eval PowerPack, которая включает в себя промежуточный перевод цитат 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)]) -> ...

Обновление 2

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

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

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

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

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

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

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

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

1 голос
/ 19 июня 2011

Вы можете написать интерпретатор, который оценит предложение и вызовет функцию 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()    

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...