f# - Can I pass a 'T[] parameter to a function that wants obj[] without using `Array.map box`? -
the short version:
i need call function in code can't modify. function takes obj[]
, , want pass 't[]
. use array.map box
, i'm trying avoid creating intermediate array. there direct way convert 't[]
obj[]
without passing through array.map box
or other code create intermediate array?
the long version:
i'm trying write code needs interoperate persistentvector class fsharpx.collections. (specifically, i'm trying implement rrb-trees in f#). persistentvector b-tree branching factor of 32. each node in tree contains 1 of 2 things: either other nodes (if node not leaf node), or items stored in tree (if node leaf node). now, natural way represent data structure in f# discriminated union type node<'t> = treenode of node[] | leafnode of 't[]
. assume performance reasons, fsharpx.collections.persistentvector code instead defines node class follows:
type node(thread,array:obj[]) = let thread = thread new() = node(ref null,array.create literals.blocksize null) 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
the threading code unrelated current problem (it's used in transient vectors, allow performance improvements), let's remove sake of creating simplest summary of problem. after removing thread-related code, have node
definition looks this:
type node(array:obj[]) = new() = node([||]) member this.array = array
i want implementation of rrb trees interoperate smoothly existing persistentvector class, because set of valid persistentvector trees strict subset of set of valid rrb trees. part of implementation, have rrbnode
class inherits node
(and therefore must take obj[]
parameter in constructor), , need create new instances of either node
or rrbnode
. example, implementation of rrbtree.ofarray
looks this:
let ofarray<'t> (arr:'t[]) = let leaves = arr |> array.chunkbysize 32 |> array.map node // more code here build tree above leaf nodes
or rather, like define that, can't. code above gives me type mismatch error on array.map node
call. node
constructor takes obj[]
, , error message reports "the type 't[]
not compatible type obj[]
".
one approach tried solve problem use box
, unbox
. https://stackoverflow.com/a/7339153/2314532 led me believe piping array of type through box
followed unbox
lead casting array obj[]
. yes, this misfeature of .net type system compromises type safety (the cast passes @ compile time might fail @ runtime) — because need interoperate node
class persistentvector, don't have benefits of type safety anyway (since node
has used obj
instead of discriminated union). 1 part of code, want tell f# compiler "stop protecting me here, please, know i'm doing , i've written extensive unit tests". attempt use box >> unbox
approach failed @ runtime:
let intarray = [|1;2;3;4;5|] let intnode = node(intarray) // doesn't compile: type mismatch. expecting obj[] got int[] let objarray : obj[] = intarray |> box |> unbox // compiles, fails @ runtime: invalidcastexception let objnode = node(objarray)
(i made type of objarray
explicit make reading minimal example easy possible, didn't need write it: f# correctly infers required type call node(objarray)
on next line. equivalent part of actual code doesn't have explicit type annotations, obj[]
array type still inferred, , it's same int[]
obj[]
cast, via |> box |> unbox
, causing invalidcastexception
in actual code.)
another approach work insert call array.map box
node
-creating pipeline:
let ofarray<'t> (arr:'t[]) = let leaves = arr |> array.chunkbysize 32 |> array.map (array.map box >> node) // more code here build tree above leaf nodes
this want (creates array of node
instances, become leaves in tree), creates intermediate array in process. i'd let chunked arrays become node arrays directly, otherwise i'll burning through o(n) memory , creating unnecessary gc pressure. i've thought using seq.cast
@ point in pipeline, i'm worried performance effects of using seq.cast
. turning arrays of known size (here, 32) seqs means other code, needs arrays (to create node
instances), have call array.ofseq
first, , array.ofseq
implemented resizearray
since can't count on size of seqs in general case. there's optimization seqs arrays, version of array.ofseq
creates new array return value (which precisely correct behavior general case, precisely i'm trying avoid here).
is there way me cast 't[]
arrays obj[]
, deliberately forfeiting type safety, without creating intermediate arrays i've been trying hard avoid? or going have write 1 bit of code in c# can unsafe things f# compiler trying protect me from?
there 2 possible outcomes depending on whether 't
value or reference type.
reference types
if 't
reference type, box
unbox
trick going work fine:
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"|]
value types
if 't
value type then, you've noticed, conversion fail @ runtime.
there no way make conversion succeed because value types in array haven't been boxed. there no possible way circumvent type system , convert obj[]
directly. going have explicitly each element.
let intarray = [|1; 2; 3; 4; 5|] let objarray : obj[] = intarray |> array.map (box)
handling both
you write generic conversion function check whether type reference or value type , perform appropriate conversion:
let converttoobjarray<'t> (arr : 't[]) = if typeof<'t>.isvaluetype arr |> array.map (box) else arr |> box |> unbox
usage:
converttoobjarray strarray
val : obj [] = [|"a"; "b"; "c"; "d"; "e"|]
converttoobjarray intarray
val : obj [] = [|1; 2; 3; 4; 5|]
Comments
Post a Comment