构建表达式树时遇到一些问题。使用代码引用时我可以做同样的事情,但我没有运气通过表达式做到这一点。
首先看看我通过代码报价
做的方法 open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
type Container<'a> = Container of 'a
type FromD<'a> = {a: Container<'a>; b: Container<'a>}
type ToD<'a> = {a: Container<'a>; b: Container<'a>}
let private eval e = QuotationEvaluator.Evaluate e
let f1 f =
let ex =
<@
fun (x:FromD<'a>) ->
{
a = f x.a;
b = f x.b
}
: ToD<'b>
@>
eval ex
上述签名为(Container<'a> -> Container<'b>) -> (FromD<'a> -> ToD<'b>)
。正是我想要的。由f1
生成的表达式树是
Lambda (x,
NewRecord (ToD`1,
Application (ValueWithName (<fun:r1@60>, f),
PropertyGet (Some (x), a, [])),
Application (ValueWithName (<fun:r1@60>, f),
PropertyGet (Some (x), b, []))))
现在有一些测试代码可以将FromD
转换为ToD
并在Container
上应用转换
let transform (Container (v:'a)) : Container<'b> = Container (sprintf "%A" v)
[<Test>]
let ``test F1`` () =
let r1 = f1 transform {a = Container true; b = Container true}
let r2 = f1 transform {a = Container 1; b = Container 2}
printfn "F1: %A, F1: %A" r1 r2
一切都与我想要的完全一样,r1
和r2
会产生预期效果。
现在我想使用表达式而不是代码引用来重新创建f1
这是我的第一次尝试(带有一些辅助函数)
//fields :: Type -> PropertyInfo []
let fields t = FSharpType.GetRecordFields t
//nameMap :: Type -> Map<string,PropertyInfo>
let nameMap t =
t
|> fields
|> Array.map (fun x -> x.Name, x)
|> Map.ofArray
let f2<'x, 't> f =
let xt = typeof<'x>
let tt = typeof<'t>
let ps = nameMap xt
let x = Var("x", xt)
let vx = Expr.Var(x)
let fnv = Expr.ValueWithName(f, "f")
let ex =
Expr.Lambda(x,
Expr.NewRecord(tt,
[
Expr.Application(fnv, Expr.PropertyGet(vx, ps.Item "a", []))
Expr.Application(fnv, Expr.PropertyGet(vx, ps.Item "b", []))
]))
let ex2 : Expr<'x -> 't> = ex |> Expr.Cast
let ex3 = eval ex2
ex3
和一些测试代码
let ``test F2`` () =
let r3 = (f2<FromD<bool>, ToD<string>> transform) {a = Container true; b = Container true}
printfn "R3 %A" r3
现在首先,在这种情况下,f2
的签名是
(Container<obj> -> Container<string>) -> ('x -> 't)
而不是
(Container<'a> -> Container<'b>) -> (FromD<'a> -> ToD<'b>)
所以不知何故,类型推断器有点急于这个。
这导致以下错误消息
System.ArgumentException : Type mismatch when building 'f': function argument type doesn't match. Expected 'tst+Container`1[System.Boolean]', but received type 'tst+Container`1[System.Object]'.
Parameter name: receivedType
at Microsoft.FSharp.Quotations.PatternsModule.checkTypesSR[a] (System.Type expectedType, System.Type receivedType, a name, System.String threeHoleSR) [0x00019] in <57acd2f6dff9fae1a7450383f6d2ac57>:0
at Microsoft.FSharp.Quotations.PatternsModule.checkAppliedLambda (Microsoft.FSharp.Quotations.FSharpExpr f, Microsoft.FSharp.Quotations.FSharpExpr v) [0x00084] in <57acd2f6dff9fae1a7450383f6d2ac57>:0
at Microsoft.FSharp.Quotations.PatternsModule.mkApplication (Microsoft.FSharp.Quotations.FSharpExpr v_0, Microsoft.FSharp.Quotations.FSharpExpr v_1) [0x00001] in <57acd2f6dff9fae1a7450383f6d2ac57>:0
at Microsoft.FSharp.Quotations.FSharpExpr.Application (Microsoft.FSharp.Quotations.FSharpExpr functionExpr, Microsoft.FSharp.Quotations.FSharpExpr argument) [0x00001] in <57acd2f6dff9fae1a7450383f6d2ac57>:0
at tst.f2[x,t] (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] f) [0x0005f] in <582303e818eafa12a7450383e8032358>:0
at tst.test F2 () [0x00005] in <582303e818eafa12a7450383e8032358>:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00038] in <8cd55ece525b4760b63de40980e005aa>:0
因此,在构造表达式树时似乎存在一些问题,因为类型inferer说我的函数有一个bool
类型参数,但实际参数是object
。
现在我可以通过重写这个函数来克服这个问题
let f2<'x, 't> f =
let xt = typeof<'x>
let tt = typeof<'t>
let ps = nameMap xt
let x = Var("x", xt)
let vx = Expr.Var(x)
let fnv = Expr.ValueWithName(f, typeof<Container<bool> -> Container<string>>, "f")
let ex =
Expr.Lambda(x,
Expr.NewRecord(tt,
[
Expr.Application(fnv, Expr.PropertyGet(vx, ps.Item "a", []))
Expr.Application(fnv, Expr.PropertyGet(vx, ps.Item "b", []))
]))
let ex2 : Expr<'x -> 't> = ex |> Expr.Cast
let ex3 = eval ex2
ex3
在这种情况下,我强制ValueWithName
为特定类型,而不是f.GetType()
我为此示例创建了一个非常特定的类型(typeof<Container<bool> -> Container<string>>
),以使示例更易于理解。
这将帮助我完成施工阶段并与演员合作 构造的表达式树也与之前相同。
但是现在它在评估期间崩溃并出现以下错误消息
System.ArgumentException : Argument types do not match
at System.Linq.Expressions.Expression.Constant (System.Object value, System.Type type) [0x00049] in <4a648327db854c86ab0ece073e38f4b3>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.LetRecConvExpr (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Core.FSharpOption`1[T] letrec, Microsoft.FSharp.Quotations.FSharpExpr inp) [0x00185] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.ConvExpr (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Quotations.FSharpExpr inp) [0x00001] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.LetRecConvExpr (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Core.FSharpOption`1[T] letrec, Microsoft.FSharp.Quotations.FSharpExpr inp) [0x02065] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.ConvExpr (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Quotations.FSharpExpr inp) [0x00001] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvExprs@703.Invoke (Microsoft.FSharp.Quotations.FSharpExpr inp) [0x00001] in <56703c1ea378c767a74503831e3c7056>:0
at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult] (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] mapping, Microsoft.FSharp.Collections.FSharpList`1[T] x) [0x0003f] in <57acd2f6dff9fae1a7450383f6d2ac57>:0
at Microsoft.FSharp.Collections.ListModule.Map[T,TResult] (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] mapping, Microsoft.FSharp.Collections.FSharpList`1[T] list) [0x00001] in <57acd2f6dff9fae1a7450383f6d2ac57>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.ConvExprs (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Collections.FSharpList`1[T] es) [0x00007] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.LetRecConvExpr (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Core.FSharpOption`1[T] letrec, Microsoft.FSharp.Quotations.FSharpExpr inp) [0x020e6] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.ConvExpr (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Quotations.FSharpExpr inp) [0x00001] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.LetRecConvExpr (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Core.FSharpOption`1[T] letrec, Microsoft.FSharp.Quotations.FSharpExpr inp) [0x027f0] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.ConvExpr (FSharp.Quotations.Evaluator.QuotationEvaluationTypes+ConvEnv env, Microsoft.FSharp.Quotations.FSharpExpr inp) [0x00001] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.Conv[a] (a e, System.Boolean eraseEquality) [0x0001d] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.CompileImpl[a] (a e, System.Boolean eraseEquality) [0x00001] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluationTypes.Compile[a] (a e) [0x00001] in <56703c1ea378c767a74503831e3c7056>:0
at FSharp.Quotations.Evaluator.QuotationEvaluator.Evaluate[T] (Microsoft.FSharp.Quotations.FSharpExpr`1[T] e) [0x00001] in <56703c1ea378c767a74503831e3c7056>:0
at tst.f2[x,t] (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] f) [0x000f5] in <5823081418eafa12a745038314082358>:0
at tst.test F2 () [0x00005] in <5823081418eafa12a745038314082358>:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00038] in <8cd55ece525b4760b63de40980e005aa>:0
有没有人知道最新情况?
答案 0 :(得分:4)
f2
的类型以'x -> 't
结尾,因为这正是您在此行中指定的方式:
let ex2 : Expr<'x -> 't> = ex |> Expr.Cast
f2
甚至不知道FromD
和ToD
等内容的存在,所以它的类型不可能有它们。
但是,如果您查看测试中r3
的第一部分的类型,您会看到它是FromD<_> -> ToD<_>
,因为它们被指定为f2
的类型参数分别为'x
和't
。
至于Container<obj>
- 它实际上比你想象的要差一些。如果您单独查看f2
,则会看到其类型为obj -> 'x -> 't
。这是因为f2
正文中没有任何内容可以暗示f
的类型应该是什么。所以它被迫obj
作为所有人的最终超类型
当您实际使用带参数f2
的<{em> transform
参数f
时 - 那时编译器会将f
的类型修复为Container<_> -> Container<string>
(因为那是transform
的类型),后来变成Container<obj> -> Container<string>
,因为程序中没有任何东西可以进一步限制类型。
从上面可以看出,修复是不言而喻的:只需明确声明f
的类型。
let f2<'x, 't, 'a, 'b> (f: Container<'a> -> Container<'b>) =
...
即使在第一次申请之前,这也会为您提供正确的类型。
但要注意!
由于您的所有处理都是在运行时进行的,因此编译器无法保证您在所有位置都键入安全性。因此,你必须小心谨慎。以下是您的代码所依赖的一些(尽管可能不是全部)可编译时可强制执行的内容:
'x
必须是包含a
类型b
和'a
字段的记录。't
必须是具有两个字段的记录,名称为a
和b
,按特定顺序声明,并且都具有'b
类型。这样的设计对我来说似乎有些不稳定。也许如果你描述了你原来的问题(最好是另外一个问题),有人可能会建议一个更优雅的解决方案。
如果您只想“映射过记录”,我或许会看一个不那么雄心勃勃的解决方案,例如:
let fromDMap f (fromD: FromD<_>) : ToD<_> = { a = f fromD.a; b = f fromD.b }
// Usage:
let r3 = fromDMap transform {a = Container true; b = Container true}
当然,如果要创建用于映射任意类型的同名字段的“通用”函数,则此方法将不起作用。但是,我冒昧地认为这样的功能有点太通用了。
P.S。您的函数transform
具有声明的类型,它比实际函数更通用。声明的返回类型为Container<'b>
,但它实际返回的是Container<string>
。因此'b
被限制为string
。