2010-08-05 3 views
6

Допустим, мы имеем простой F # цитата:Генерация параметризованные 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) @@ >. Это возможно? Или это только выбор, чтобы вручную применить сопоставление шаблонов с генерируемой цитатой и преобразовать ее в то, что мне нужно?

ОБНОВЛЕНИЕ. В качестве обходного пути я осуществил следующий адаптер:

 
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-й форме. Но мне интересно узнать, может ли это быть достигнуто простым способом, без пересечения деревьев выражений.

ответ

6

Я не думаю, что это можно будет сделать; во втором случае вы вставляете выражение <@ (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! Однако, учитывая ограниченный диапазон форм выражений, сгенерированных компилятором, ваша сломанная версия не может быть проблематичной на практике.

EDIT

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

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

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) @> 
+0

Благодарим за ответ и большое предложение. Тем не менее, я все еще придерживаюсь связанной проблемы: возможно ли вообще подключить простой делегат F # (например, fun x -> x.Name) к общей цитате, которая не зависит от фактического типа, например, цитата № 2 выше. Это похоже на то, что делают насмешливые фреймворки: они определяют некоторые выражения без знания конкретных интерфейсов и ввода конкретных типов. Кажется, я не могу этого добиться в F #. –

+0

@Vagif - Я не уверен, что понимаю ваш вопрос. Не могли бы вы подробнее рассказать о том, что вы пытаетесь сделать? – kvb

+0

Мне нужно отправить interop между F # и C#. C# ожидает выражения LINQ определенного типа. Я могу записать его в F # жестко закодированным образом, но для этого хочу создать общую оболочку F #. Эта обертка должна иметь возможность принимать в качестве входных lambdas как «fun x -> x.Name» и преобразовывать их в цитаты. Я начинаю подозревать, что это невозможно в общем случае. –