我可以将一个' T []参数传递给想要obj []而不使用`Array.map box`的函数吗?

时间:2017-04-06 10:59:54

标签: arrays f# covariance

简短版本:

我需要在我无法修改的代码中调用函数。该函数需要obj[],我想将其传递给'T[]。我可以使用Array.map box,但我试图避免创建一个中间数组。有没有直接的方法将'T[]转换为obj[]而不通过Array.map box或任何其他会创建中间数组的代码?

长版:

我尝试编写需要与PersistentVector class from FSharpx.Collections互操作的代码。 (具体来说,我试图在F#中实现RRB-Trees)。 PersistentVector基本上是一个B树,其分支因子为32.树中的每个节点都包含两个内容之一:其他节点(如果节点不是叶节点),或者存储在树中的项(如果节点)是一个叶子节点)。现在,在F#中表示此数据结构的最自然的方式是使用像type Node<'T> = TreeNode of Node[] | LeafNode of 'T[]这样的区分联合。但是我假设是性能原因,FSharpx.Collections.PersistentVector代码定义了它的Node类,如下所示:

type Node(thread,array:obj[]) =
    let thread = thread
    new() = Node(ref null,Array.create Literals.blockSize null)
    with
        static member InCurrentThread() = Node(ref Thread.CurrentThread,Array.create Literals.blockSize null)
        member this.Array = array
        member this.Thread = thread
        member this.SetThread t = thread := t

线程代码与我当前的问题无关(它在瞬态向量中使用,它允许某些性能改进),因此,为了创建问题的最简单摘要,请删除它。删除与线程相关的代码后,我们有一个Node定义,如下所示:

type Node(array:obj[]) =
    new() = Node([||])
    with member this.Array = array

我希望RRB树的实现能够与现有的PersistentVector类平滑地进行互操作,因为所有有效PersistentVector树的集合都是所有有效RRB树集的严格子集。作为该实现的一部分,我有一个继承自RRBNode的{​​{1}}类(因此也必须在其构造函数中使用Node参数),我经常需要创建新的实例obj[]Node。例如,我RRBNode的实现基本上是这样的:

RRBTree.ofArray

或者更确切地说,我会喜欢来定义它,但我不能。上面的代码在let ofArray<'T> (arr:'T[]) = let leaves = arr |> Array.chunkBySize 32 |> Array.map Node // More code here to build a tree above those leaf nodes 调用中给出了类型不匹配错误。 Array.map Node构造函数使用Node,错误消息报告&#34;类型obj[]与类型'T[]&#34;不兼容。

我尝试解决此问题的一种方法是使用obj[]boxhttps://stackoverflow.com/a/7339153/2314532让我相信通过unbox后跟box来管理任何类型的数组会导致将该数组转换为unbox。是的,this is basically a misfeature of the .Net type system会危及类型安全(在编译时传递的强制转换可能会在运行时失败) - 但是因为我需要与PersistentVector中的obj[]类进行互操作,所以我不能无论如何都类型安全的好处(因为Node使用Node而不是歧视联盟。所以对于我的代码的这一部分,我实际上想告诉F#编译器&#34;停止在这里保护我,拜托,我知道我在做什么,并且我已经编写了大量的单元测试&#34; 。但是我尝试使用obj方法在运行时失败了:

box >> unbox

(我将let intArray = [|1;2;3;4;5|] let intNode = Node(intArray) // Doesn't compile: Type mismatch. Expecting obj[] but got int[] let objArray : obj[] = intArray |> box |> unbox // Compiles, but fails at runtime: InvalidCastException let objNode = Node(objArray) 的类型显式化为尽可能简单地阅读这个最小的例子,但我不需要编写它:F#正确地从调用{{{ 1}}在下一行。我的实际代码的等效部分没有明确的类型注释,但objArray数组类型仍然是推断的,并且它是相同的{{1} } Node(objArray)通过obj[]投射,导致我的实际代码中出现int[]。)

可能有效的另一种方法是将obj[]的调用插入我的|> box |> unbox - 创建管道:

InvalidCastException

这就是我想要的(创建一个Array.map box实例数组,它将成为树中的叶子),但它会在进程中创建一个额外的中间数组。我想让分块的数组直接成为的节点数组,否则我将烧掉O(N)内存并产生不必要的GC压力。我已经考虑过在管道中的某个时刻使用Node,但我担心使用let ofArray<'T> (arr:'T[]) = let leaves = arr |> Array.chunkBySize 32 |> Array.map (Array.map box >> Node) // More code here to build a tree above those leaf nodes 的性能影响。将已知大小的数组(此处为32)转换为seqs意味着需要数组(创建Node个实例)的其他代码必须首先调用Seq.cast,并实现Seq.cast使用Node,因为在一般情况下它不能指望seq的大小。对已经是数组的seq进行了优化,但即使是那个版本的Array.ofSeq也会创建一个新数组作为其返回值(这正是一般情况下的正确行为,但正是我所说的那样#39} ;我试图避免在这里。)

我有没有办法将我的Array.ofSeq数组投射到ResizeArray故意放弃类型安全,而不创建我尝试过的中间数组这么难以避免?或者我将不得不在C#中编写这一段代码,这样我才能做出F#编译器试图保护我的不安全的事情?

1 个答案:

答案 0 :(得分:5)

根据'T是值还是引用类型,有两种可能的结果。

参考类型

如果'T是参考类型,那么您的box unbox技巧就可以正常使用了:

let strArray = [|"a";"b";"c";"d";"e"|]
let objArray : obj[] = strArray |> box |> unbox
val strArray : string [] = [|"a"; "b"; "c"; "d"; "e"|]
val objArray : obj [] = [|"a"; "b"; "c"; "d"; "e"|]

价值类型

如果'T是值类型,那么,正如您所注意到的那样,转换将在运行时失败。

没有办法使转换成功,因为数组中的值类型尚未加框。没有办法绕过类型系统并直接转换为obj[]。您将必须为每个元素明确地执行此操作。

let intArray = [|1; 2; 3; 4; 5|]
let objArray : obj[] = intArray |> Array.map (box)

同时处理

您可以编写通用转换函数来检查类型是引用还是值类型,然后执行适当的转换:

let convertToObjArray<'T> (arr : 'T[]) =
    if typeof<'T>.IsValueType then
        arr |> Array.map (box)
    else
        arr |> box |> unbox

用法:

convertToObjArray strArray
val it : obj [] = [|"a"; "b"; "c"; "d"; "e"|]
convertToObjArray intArray
val it : obj [] = [|1; 2; 3; 4; 5|]