Edit 2021:
Yes you can since F#5.0 see Docs
Original Question:
I would like to use negative indices on Arrays so that i can write myThings.[-2] <- "sth"
to set the second last item. Is this possible?
I tried this but it fails to compile with:
Method overrides and interface implementations are not permitted here
type ``[]``<'T> with
/// Allows for negative index too (like python)
override this.Item
with get i = if i<0 then this.[this.Length+i] else this.[i]
and set i v = if i<0 then this.[this.Length+i] <- v else this.[i] <- v
I know, I could use new members like myThings.SetItemNegative(-2,"sth")
but this is not as nice as using the index notation:
type ``[]``<'T> with
/// Allows for negative index too (like python)
member this.GetItemNegative (i) =
if i<0 then this.[this.Length+i] else this.[i]
member this.SetItemNegative (i,v) =
if i<0 then this.[this.Length+i] <- v else this.[i] <- v
Unfortunately existing methods in a type have priority over future extension members.
It doesn't make so much sense but that's the way currently is, you can read more about it in this issue: https://github.com/dotnet/fsharp/issues/3692#issuecomment-334297164
That's why if you define such extension it will be ignored, and what's worst: silently ignored !
Anyway there are some proposals to add something similar to negative slices to F#.
Gus explained that existing members of 'T array
can not be overwritten.
A workaround is extending 'T seq
.
For my F# scripts this is good enough. I am not sure if this is a good idea in general though.
open System
open System.Collections.Generic
open System.Runtime.CompilerServices
//[<assembly:Extension>] do()
/// Converts negative indices to positive ones
/// e.g.: -1 is last item .
let negIdx i len =
let ii = if i<0 then len+i else i
if ii<0 || ii >= len then failwithf "Cannot get index %d of Seq with %d items" i len
ii
let get i (xs:seq<'T>) : 'T =
match xs with
//| :? ('T[]) as xs -> xs.[negIdx i xs.Length] // covered by IList
| :? ('T IList) as xs -> xs.[negIdx i xs.Count]
| :? ('T list) as xs -> List.item (negIdx i (List.length xs)) xs
| _ -> Seq.item (negIdx i (Seq.length xs)) xs
let set i x (xs:seq<_>) :unit =
match xs with
| :? ('T[]) as xs -> xs.[negIdx i xs.Length]<- x // why not covered by IList?
| :? ('T IList) as xs -> xs.[negIdx i xs.Count] <- x
| _ -> failwithf "Cannot set items on this Seq (is it a dict, lazy or immutable ?)"
//[<Extension>]
type Collections.Generic.IEnumerable<'T> with
[<Extension>]
///Allows for negtive indices too (like Python)
member this.Item
with get i = get i this
and set i x = set i x this
///Allows for negative indices too.
///The resulting seq includes the item at slice-ending-index. like F# range expressions include the last integer e.g.: 0..5
[<Extension>]
member this.GetSlice(startIdx,endIdx) : 'T seq = // to use slicing notation e.g. : xs.[ 1 .. -2]
let count = Seq.length this
let st = match startIdx with None -> 0 | Some i -> if i<0 then count+i else i
let len = match endIdx with None -> count-st | Some i -> if i<0 then count+i-st+1 else i-st+1
if st < 0 || st > count-1 then
let err = sprintf "GetSlice: Start index %d is out of range. Allowed values are -%d up to %d for Seq of %d items" startIdx.Value count (count-1) count
raise (IndexOutOfRangeException(err))
if st+len > count then
let err = sprintf "GetSlice: End index %d is out of range. Allowed values are -%d up to %d for Seq of %d items" endIdx.Value count (count-1) count
raise (IndexOutOfRangeException(err))
if len < 0 then
let en = match endIdx with None -> count-1 | Some i -> if i<0 then count+i else i
let err = sprintf "GetSlice: Start index '%A' (= %d) is bigger than end index '%A'(= %d) for Seq of %d items" startIdx st endIdx en count
raise (IndexOutOfRangeException(err))
this |> Seq.skip st |> Seq.take len
// usage :
let modify (xs:seq<_>) =
xs.[-1] <- 0 // set last
xs.[-2] <- 0 // set second last
xs
let slice (xs:seq<_>) =
xs.[-3 .. -1] // last 3 items
modify [|0..9|]
slice [|0..9|]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With