Генерация параметризованных цитат F # - PullRequest
6 голосов
/ 05 августа 2010

Допустим, у нас есть простая цитата F #:

type Pet = {  Name : string }
let exprNonGeneric = <@@ System.Func(fun (x : Pet) -> x.Name) @@>

Полученная цитата выглядит так:

val exprNonGeneri : Expr =
  NewDelegate (System.Func`2[[FSI_0152+Pet, FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]],
             x, PropertyGet (Some (x), System.String Name, []))

Теперь я хочу обобщить его, чтобы вместо типа «Pet» и свойства «Name» я мог использовать произвольный тип и метод / свойство, определенные для него. Вот что я пытаюсь сделать:

let exprGeneric<'T, 'R> f = <@@ System.Func<'T, 'R>( %f ) @@>
let exprSpecialized = exprGeneric<Pet, string> <@ (fun (x : Pet) -> x.Name) @>

Полученное выражение теперь другое:

val exprSpecialized : Expr =
  NewDelegate (System.Func`2[[FSI_0152+Pet, FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]],
             delegateArg,
             Application (Lambda (x,
                                  PropertyGet (Some (x), System.String Name, [])),
                          delegateArg))

Как видите, разница между первым и вторым выражением заключается в том, что в первом случае выражение NewDelegate верхнего уровня содержит PropertyGet, а второе выражение оборачивает PropertyGet в выражение Application / Lambda. И когда я передаю это выражение во внешний код, оно не ожидает такой структуры выражения и завершается ошибкой.

Так что мне нужен какой-то способ построения обобщенной версии цитаты, поэтому, когда она становится специализированной, результирующая цитата точно соответствует <@@ System.Func (fun (x: Pet) -> x.Name) @ @>. Это возможно? Или есть только один вариант - вручную применить сопоставление с образцом к сгенерированной цитате и преобразовать его в то, что мне нужно?

UPDATE . В качестве обходного пути я реализовал следующий адаптер:

let convertExpr (expr : Expr) =
    match expr with
    | NewDelegate(t, darg, appl) ->
        match (darg, appl) with
        | (delegateArg, appl) ->
            match appl with 
            | Application(l, ldarg) ->
                match (l, ldarg) with
                | (Lambda(x, f), delegateArg) ->
                    Expr.NewDelegate(t, [x], f)
                | _ -> expr
            | _ -> expr
    | _ -> expr

Это делает свою работу - теперь я могу преобразовать выражение из 1-й формы во 2-ю. Но мне интересно узнать, можно ли это сделать простым способом, не обходя деревья выражений.

1 Ответ

6 голосов
/ 05 августа 2010

Я не думаю, что это будет возможно сделать;во втором случае вы вставляете выражение <@ (fun (x : Pet) -> x.Name) @>, которое представлено с помощью узла Lambda, в отверстие в другом выражении.Компилятор не упрощает выражения во время этого процесса подключения, поэтому узел Lambda не будет удален, независимо от того, что вы делаете.

Однако ваш обходной путь сопоставления с образцом может быть значительно упрощен:

let convertExpr = function
| NewDelegate(t, [darg], Application(Lambda(x,f), Var(arg))) 
    when darg = arg -> Expr.NewDelegate(t, [x], f)
| expr -> expr

На самом деле, ваша более сложная версия неверна.Это потому, что delegateArg в вашем самом внутреннем шаблоне не совпадает со значением ранее связанного идентификатора delegateArg из внешнего шаблона;это новый, недавно связанный идентификатор, который также называется delegateArg.На самом деле внешний идентификатор delegateArg имеет тип Var list, а внутренний - Expr!Однако, учитывая ограниченный диапазон форм выражений, сгенерированных компилятором, ваша испорченная версия на практике может не быть проблемной.у вас правильно может не получиться добиться того, чего вы хотите.В отличие от C #, где x => x + 1 может интерпретироваться как имеющий тип Func<int,int> или Expression<Func<int,int>>, в F # fun x -> x + 1 всегда имеет тип int->int.Если вы хотите получить значение типа Expr<int->int>, вам, как правило, нужно использовать оператор кавычек (<@ @>).

Однако есть одна альтернатива, которая может быть полезной.Вы можете использовать атрибут [<ReflectedDefinition>] в функциях let bound, чтобы сделать их цитаты доступными.Вот пример:

open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.ExprShape
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns

let rec exprMap (|P|_|) = function
| P(e) -> e
| ShapeVar(v) -> Expr.Var v
| ShapeLambda(v,e) -> Expr.Lambda(v, exprMap (|P|_|) e)
| ShapeCombination(o,l) -> RebuildShapeCombination(o, l |> List.map (exprMap (|P|_|)))


let replaceDefn = function
| Call(None,MethodWithReflectedDefinition(e),args) 
    -> Some(Expr.Applications(e, [args]))
| _ -> None


(* plugs all definitions into an expression *)
let plugDefs e = exprMap replaceDefn e

[<ReflectedDefinition>]
let f x = x + 1

(* inlines f into the quotation since it uses the [<ReflectedDefinition>] attribute *)
let example = plugDefs <@ fun y z -> (f y) - (f 2) @>
...