Arrays 使用非类型化的F#引号在不知道类型的情况下复制数组
我正在做一个小项目,用引号来克隆一些基本记录类型的树,在大多数情况下我都能做到,最大的问题是数组Arrays 使用非类型化的F#引号在不知道类型的情况下复制数组,arrays,f#,quotations,Arrays,F#,Quotations,我正在做一个小项目,用引号来克隆一些基本记录类型的树,在大多数情况下我都能做到,最大的问题是数组 module FSharpType = /// predicate for testing types to see if they are generic option types let IsOption (stype: System.Type) = stype.Name = "FSharpOption`1" /// predicate for testing type
module FSharpType =
/// predicate for testing types to see if they are generic option types
let IsOption (stype: System.Type) = stype.Name = "FSharpOption`1"
/// predicate for testing types to see if they are generic F# lists
let IsList (stype: System.Type) = stype.Name = "FSharpList`1"
module RecordCloning =
let inline application prms expr = Expr.Application(expr, prms)
let inline coerse typ expr = Expr.Coerce(expr, typ)
let (|IsMapType|_|) (t: Type) =
if t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<Map<_,_>> then Some t
else None
let rec copyThing (mtype: Type) : Expr =
match mtype with
| _ when FSharpType.IsRecord mtype -> genRecordCopier mtype
| _ when FSharpType.IsUnion mtype -> genUnionCopier mtype
| _ when mtype.IsValueType || mtype = typeof<String> -> <@@ id @@>
| _ when mtype.IsArray -> genArrayCopier mtype
| IsMapType t -> <@@ id @@>
| _ when mtype = typeof<System.Object> -> <@@ id @@>
| _ -> failwithf "Unexpected Type: %s" (mtype.ToString())
and genRecordCopier (rtype: Type) : Expr =
let arg = Var("x", typeof<obj>, false)
let argExpr = Expr.Var(arg)
let useArg = Expr.Coerce(argExpr, rtype)
let fields = FSharpType.GetRecordFields(rtype)
let members = [ for field in fields -> genFieldCopy useArg field ]
let newrec = Expr.Coerce(Expr.NewRecord(rtype, members),typeof<obj>)
Expr.Lambda(arg, newrec)
and genFieldCopy argExpr (field: PropertyInfo) : Expr =
let pval = Expr.PropertyGet(argExpr, field)
let convfun = copyThing field.PropertyType
let applied = Expr.Application (convfun, Expr.Coerce(pval, typeof<obj>))
Expr.Coerce(applied, field.PropertyType)
and castToType (atype : Type) : Expr =
let arg = Var("x", typeof<obj>, false)
let argExpr = Expr.Var(arg)
Expr.Lambda(arg, Expr.Coerce(argExpr, atype))
and coerseLambda (outterType: Type) (lambda: Expr) : Expr =
let arg = Var("x", outterType, false)
let argExpr = Expr.Var(arg)
let wrappedLambda =
lambda
|> application (argExpr |> coerse typeof<obj>)
|> coerse outterType
Expr.Lambda(arg, wrappedLambda)
and genArrayCopier (atype : Type) : Expr =
let etype = atype.GetElementType()
let copyfun = copyThing etype
let arg = Var("arr", typeof<obj>, false)
let argExpr = Expr.Var(arg) |> coerse atype
let wrappedLambda = coerseLambda etype copyfun
let func = <@@ Array.map (%%wrappedLambda) (%%argExpr) @@>
Expr.Lambda(arg, func)
and genOptionCopier (otype: Type) : Expr =
let etype = otype.GetGenericArguments().[0]
let copyfun = copyThing etype
<@@ fun (inobj: obj) ->
let x = inobj :?> Option<'t>
match x with
| Some v -> Some <| (%%copyfun) (box v)
| None -> None
|> box
@@>
and genUnionCopier (utype: Type) : Expr =
let cases = FSharpType.GetUnionCases utype
// if - union case - then - copy each field into new case - else - next case
let arg = Var("x", typeof<obj>, false)
let argExpr = Expr.Var(arg)
let useArg = Expr.Coerce(argExpr, utype)
let genCaseTest case = Expr.UnionCaseTest (useArg, case)
let makeCopyCtor (ci: UnionCaseInfo) =
let copiedMembers = [ for field in ci.GetFields() -> genFieldCopy useArg field ]
Expr.Coerce(Expr.NewUnionCase(ci, copiedMembers), typeof<obj>)
let genIf ifCase thenCase elseCase = Expr.IfThenElse(ifCase, thenCase, elseCase)
let nestedIfs =
cases
|> Array.map (fun case -> genIf (genCaseTest case) (makeCopyCtor case))
|> Array.foldBack (fun iff st -> iff st) <| <@@ failwith "Unexpected Case Condition" @@>
let newunion = Expr.Coerce(nestedIfs,typeof<obj>)
Expr.Lambda(arg, newunion)
let wrapInType<'I,'O> (lambdaExpr: Expr) : Expr<'I -> 'O> =
<@ fun (v : 'I) -> (%%lambdaExpr : obj -> obj) (box v) :?> 'O @>
let toLinq<'I,'O> (expr: Expr<'I -> 'O>) =
let linq = Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToExpression expr
let call = linq :?> MethodCallExpression
let lambda = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'I,'O>>(lambda.Body, lambda.Parameters)
let genrateRecordDeepCopyFunction<'T> () : ('T -> 'T) =
let expr = genRecordCopier typeof<'T>
let castExpr : Expr<obj -> obj> = expr |> Expr.Cast
let compiledExpr = (castExpr |> toLinq).Compile()
fun (v : 'T) -> compiledExpr.Invoke(box v) :?> 'T
模块FSharpType=
///测试类型的谓词,以查看它们是否为泛型选项类型
let IsOption(stype:System.Type)=stype.Name=“FSharpOption`1”
///测试类型的谓词,以查看它们是否为泛型F#列表
let IsList(stype:System.Type)=stype.Name=“FSharpList`1”
模块记录克隆=
让内联应用程序prms expr=expr.application(expr,prms)
让内联系数类型expr=expr.concure(expr,类型)
let(| IsMapType | | |)(t:Type)=
如果t.IsGenericType&&t.GetGenericTypeDefinition()=typedefof,则某些t
没有别的
let rec copyThing(mtype:Type):Expr=
将mtype与匹配
|当FSharpType.IsRecord mtype->genRecordCopier mtype时
|当FSharpType.IsUnion mtype->genunioncore mtype
|_uype.IsValueType | | mtype=typeof->
|当mtype.IsArray->genArrayCopier mtype
|IsMapType t->
|当mtype=typeof->
|->failwithf“意外类型:%s”(mtype.ToString())
和genRecordCopier(rtype:Type):Expr=
设arg=Var(“x”,typeof,false)
设argExpr=Expr.Var(arg)
让useArg=Expr.concure(argExpr,rtype)
let fields=FSharpType.GetRecordFields(rtype)
let members=[字段中的字段->genFieldCopy useArg字段]
让newrec=Expr.concure(Expr.NewRecord(rtype,members),typeof)
表达式Lambda(arg,newrec)
和genFieldCopy argExpr(字段:PropertyInfo):Expr=
设pval=Expr.PropertyGet(argExpr,field)
让convfun=copyThing field.PropertyType
let applicated=Expr.Application(convfun,Expr.胁迫(pval,typeof))
表达式强制(已应用,字段.属性类型)
和castToType(atype:Type):Expr=
设arg=Var(“x”,typeof,false)
设argExpr=Expr.Var(arg)
表达式Lambda(参数,表达式强制(argExpr,atype))
和coerseLambda(outterType:Type)(lambda:Expr):Expr=
设arg=Var(“x”,outterType,false)
设argExpr=Expr.Var(arg)
让wrappedLambda=
兰姆达
|>应用程序(argExpr |>coerse类型)
|>腔外型
表达式Lambda(arg,wrappedLambda)
和genArrayCopier(atype:Type):Expr=
让etype=atype.GetElementType()
let copyfun=copyThing词组
设arg=Var(“arr”,typeof,false)
设argExpr=Expr.Var(arg)|>coerseatype
让wrappedLambda=coerseLambda etype copyfun
设func=
表达式Lambda(参数,函数)
和GenoptionCoiler(otype:Type):Expr=
让etype=otype.GetGenericArguments()[0]
let copyfun=copyThing词组
设x=inobj:?>选项'O>=
让toLinq(expr:expr)=
让linq=Microsoft.FSharp.linq.RuntimeHelpers.leaexpressionconverter.QuotationToExpression expr
让call=linq:?>MethodCallExpression
让lambda=call.Arguments。[0]:?>LambdaExpression
表达式.Lambda(Lambda.Body,Lambda.Parameters)
让genrateRecordDeepCopyFunction'T)=
让expr=genRecordCopier类型的compiledExpr.Invoke(框v):?>'T
我尝试过几种方法,但我总是抱怨想要(string->string)但得到(obj->obj)或者想要(object[]->object[])但得到(string[]->string[])。有什么想法吗
下面是一个简单的测试用例
type SimpleArrayRecord = { Names: string array }
[<Fact>]
let ``record cloning should be able to clone a record with a simple array`` () =
let sr = { Names = [|"Rick"; "David"; "Mark"; "Paul"; "Pete"|] }
let func = RecordCloning.genrateRecordDeepCopyFunction<SimpleArrayRecord>()
let res = func sr
Assert.Equal(sr, res)
type SimpleArrayRecord={Names:string array}
[]
let``记录克隆应该能够使用简单数组`()克隆记录=
让sr={Names=[|“Rick”;“David”;“Mark”;“Paul”;“Pete”}
设func=RecordCloning.genrateRecordDeepCopyFunction()
设res=func-sr
断言相等(sr、res)
这是让我走得最远的方法。问题似乎是我无法让它将数组类型化,因此在尝试构建记录时,它总是在强制转换时失败。在理解中添加一个演员阵容没有帮助
and genArrayCopier (atype : Type) : Expr =
let etype = atype.GetElementType()
let copyfun = copyThing etype
let arg = Var("arr", typeof<obj>, false)
let argExpr = Expr.Var(arg) |> coerse atype
<@@ fun (inobj: obj) ->
let arr = inobj :?> obj[] in
[| for i = 0 to arr.Length - 1 do yield (%%copyfun) (Array.get arr i) |] |> box @@>
和genArrayCopier(atype:Type):Expr=
让etype=atype.GetElementType()
let copyfun=copyThing词组
设arg=Var(“arr”,typeof,false)
设argExpr=Expr.Var(arg)|>coerseatype
设arr=inobj:?>obj[]in
[|对于i=0到arr.Length-1,不产生(%%copyfun)(Array.get arr i)|]\124;>box@@@
以下Toyvo的解决方案适用于上述示例,但不适用于记录数组:
type SimpleRecord = { Name: string; Age: int }
type LotsOfRecords = { People: SimpleRecord [] }
[<Fact>]
let ``record cloning should be able to clone a record with an array of records`` () =
let sr = { People = [|{Name = "Rick"; Age = 33 }; { Name = "Paul"; Age = 55 }|] }
let func = RecordCloning.genrateRecordDeepCopyFunction<LotsOfRecords>()
let res = func sr
Assert.Equal(sr, res)
type SimpleRecord={Name:string;Age:int}
键入LotsOfRecords={People:SimpleRecord[]}
[]
let``记录克隆应能够克隆具有记录数组的记录``()=
让sr={People=[{Name=“Rick”;Age=33};{Name=“Paul”;Age=55}}
设func=RecordCloning.genrateRecordDeepCopyFunction()
设res=func-sr
断言相等(sr、res)
对于那些后来来的人,这里是工作代码。我删除了这个选项,并没有花时间清理它,但它在其他方面相当不错
let inline application prms expr = Expr.Application(expr, prms)
let inline coerse typ expr = Expr.Coerce(expr, typ)
let inline newrec typ args = Expr.NewRecord(typ, args)
let (|IsMapType|_|) (t: Type) =
if t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<Map<_,_>> then Some t
else None
let rec copyThing (mtype: Type) : Expr =
match mtype with
| _ when FSharpType.IsRecord mtype -> genRecordCopier mtype
| _ when FSharpType.IsUnion mtype -> genUnionCopier mtype
| _ when mtype.IsValueType || mtype = typeof<String> -> getIdFunc mtype
| _ when mtype.IsArray -> genArrayCopier mtype
| IsMapType t -> getIdFunc mtype
| _ when mtype = typeof<System.Object> -> getIdFunc mtype
| _ -> failwithf "Unexpected Type: %s" (mtype.ToString())
and X<'T> : 'T = Unchecked.defaultof<'T>
and getMethod =
function
| Patterns.Call (_, m, _) when m.IsGenericMethod -> m.GetGenericMethodDefinition()
| Patterns.Call (_, m, _) -> m
| _ -> failwith "Incorrect getMethod Pattern"
and getIdFunc itype =
let arg = Var("x", itype, false)
let argExpr = Expr.Var(arg)
let func =
let m = (getMethod <@ id X @>).MakeGenericMethod([|itype|])
Expr.Call(m, [argExpr])
Expr.Lambda(arg, func)
and genRecordCopier (rtype: Type) : Expr =
let arg = Var("x", rtype, false)
let argExpr = Expr.Var(arg) //|> coerse rtype
let newrec =
FSharpType.GetRecordFields(rtype) |> Array.toList
|> List.map (fun field -> genFieldCopy argExpr field)
|> newrec rtype
Expr.Lambda(arg, newrec)
and genFieldCopy argExpr (field: PropertyInfo) : Expr =
let pval = Expr.PropertyGet(argExpr, field)
copyThing field.PropertyType |> application pval
and genArrayCopier (atype : Type) : Expr =
let etype = atype.GetElementType()
let copyfun = copyThing etype
let arg = Var("arr", atype, false)
let argExpr = Expr.Var(arg)
let func =
let m = (getMethod <@ Array.map X X @>).MakeGenericMethod([|etype; etype|])
Expr.Call(m, [copyfun; argExpr])
Expr.Lambda(arg, func)
and genUnionCopier (utype: Type) : Expr =
let cases = FSharpType.GetUnionCases utype
// if - union case - then - copy each field into new case - else - next case
let arg = Var("x", utype, false)
let useArg = Expr.Var(arg)
let genCaseTest case = Expr.UnionCaseTest (useArg, case)
let makeCopyCtor (ci: UnionCaseInfo) =
let copiedMembers = [ for field in ci.GetFields() -> genFieldCopy useArg field ]
Expr.NewUnionCase(ci, copiedMembers)
let genIf ifCase thenCase elseCase = Expr.IfThenElse(ifCase, thenCase, elseCase)
let typedFail (str: string) =
let m = (getMethod <@ failwith str @>).MakeGenericMethod([|utype|])
Expr.Call(m, [ <@ str @> ])
let nestedIfs =
cases
|> Array.map (fun case -> genIf (genCaseTest case) (makeCopyCtor case))
|> Array.foldBack (fun iff st -> iff st) <| (typedFail "Unexpected Case in Union")
Expr.Lambda(arg, nestedIfs)
让内联应用程序prms expr=expr.application(expr,prms)
让内联系数类型expr=expr.concure(expr,类型)
让内联newrec typ args=Expr.NewRecord(typ,args)
let(| IsMapType | | |)(t:Type)=
如果t.IsGenericType&&t.GetGenericTypeDefinition()=typedefof,则某些t
没有别的
let rec copyThing(mtype:Type):Expr=
将mtype与匹配
|当FSharpType.IsRecord mtype->genRecordCopier mtype时
|当FSharpType.IsUnion mtype->genunioncore mtype
|_uype.IsValueType | | mtype=typeof->getIdFunc mtype时
|当mtype.IsArray->genArrayCopier mtype
|IsMapType t->getIdFunc mtype
and getMethod q =
match q with
| Patterns.Call (_, m, _) ->
if m.IsGenericMethod then
m.GetGenericMethodDefinition()
else
m
| _ -> failwith "getMethod"
and X<'T> : 'T =
Unchecked.defaultof<'T>
and genArrayCopier (atype : Type) : Expr =
let etype = atype.GetElementType()
let copyfun = copyThing etype
let arg = Var("arr", typeof<obj>, false)
let argExpr = Expr.Var(arg) |> coerse atype
let wrappedLambda = coerseLambda etype copyfun
let func =
let m = getMethod <@ Array.map X X @> // obtained (forall 'X 'Y, 'X[] -> 'Y[])
let m = m.MakeGenericMethod([| etype; etype |]) // specialized to 'E[] -> 'E[]
Expr.Call(m, [wrappedLambda; argExpr]) // now this type-checks
Expr.Lambda(arg, func)