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

Popular posts from this blog

commonjs - How to write a typescript definition file for a node module that exports a function? -

openid - Okta: Failed to get authorization code through API call -

ios - Change Storyboard View using Seague -