Peter Zeller
Peter Zeller

Reputation: 2296

Extend F# Arrays with lookup for bigint

I would like to extend F# Arrays such that I can use arrays without converting to the finite int. Instead I want to work with bigint directly.

I was able to add a second length method to the array type as follows:

type 'T ``[]`` with

  member this.LengthI: bigint = 
    bigint this.Length

  member this.Item(index: bigint): 'T = 
    this.[int index]

However the Item method cannot be called with the .[ ] syntax.

Any ideas how this could be achieved? I this possible at all?

Upvotes: 2

Views: 157

Answers (1)

piaste
piaste

Reputation: 2058

I strongly suspect this isn't possible for native arrays. You can verify yourself that you can overload indexed access just fine for other collections.

If you compile the following code:

let myArray = [| "a" |]
let myList = [ "a" ]

let arrayElement = myArray.[11111]
let listElement = myList.[22222]

and inspect the resulting IL, you'll see that while accessing the list element compiles to a regular virtual call, there is a special CIL instruction for accessing a native array element, ldelem.

//000004: let arrayElement = myArray.[11111]
    IL_002c:  call       string[] Fuduoqv1565::get_myArray()
    IL_0031:  ldc.i4     0x2b67
    IL_0036:  ldelem     [mscorlib]System.String
    IL_003b:  stsfld     string '<StartupCode$51dff40d-e00b-40e4-b9cc-15309089d437>'.$Fuduoqv1565::arrayElement@4
    .line 5,5 : 1,33 ''
//000005: let listElement = myList.[22222]
    IL_0040:  call       class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<string> Fuduoqv1565::get_myList()
    IL_0045:  ldc.i4     0x56ce
    IL_004a:  callvirt   instance !0 class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<string>::get_Item(int32)
    IL_004f:  stsfld     string '<StartupCode$51dff40d-e00b-40e4-b9cc-15309089d437>'.$Fuduoqv1565::listElement@5
    IL_0054:  ret

I would guess that the same compiler logic that special-case array access to that single instruction also bypass any overload resolution involving extension methods and the like.

One way to circumvent this is to wrap the array in a custom type, where overloaded indexers will work as you expect. Making the wrapper type a struct should reduce the performance loss in most cases:

type [<Struct>] BigArray<'T>(array : 'T[]) = 

  member this.LengthI: bigint = 
    bigint array.Length

  member this.Item
      with get(index : int) = array.[index]
      and set (index : int) value = array.[index] <- value

  member this.Item
      with get(index : bigint) = array.[int index]
      and set (index : bigint) value = array.[int index] <- value

let bigArray = BigArray myArray
let bigArrayElement = bigArray.[0]
let bigArrayElement2 = bigArray.[bigint 0]

Another one is to upcast the array to the base System.Array class, on which you can then define the same overloaded operator. This removes the need to create a wrapper type and duplicate all members of 'T[], as you can just upcast/downcast the same array object as necessary. However, since the base class is untyped, you will lose type safety and have to box/unbox the elements when using the indexed access, which is quite ugly:

type System.Array with

  member this.Item
      with get (index : int) = (this :?> 'T[]).[index]
      and set  (index : int) (value : 'T) = (this :?> 'T[]).[index] <- value

  member this.Item
      with get(index : bigint) : 'T  = (this :?> 'T[]).[int index]
      and set(index : bigint) (value : 'T) = (this :?> 'T[]).[int index] <- value

let untypedArray = myArray :> System.Array
let untypedArrayElement = box untypedArray.[0] :?> string
let untypedArrayElement2 = box untypedArray.[bigint 0] :?> string

Upvotes: 2

Related Questions