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)