From 4cdff2b0140e3754674ae423280841df11fac6a4 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 19 Jun 2023 17:29:09 +0200 Subject: [PATCH] WIP: Added standard module functions for ImmutableArray for FSharp.Core --- src/FSharp.Core/FSharp.Core.fsproj | 11 + src/FSharp.Core/FSharp.Core.nuspec | 8 +- src/FSharp.Core/immutablearray.fs | 490 +++++++++++++++++++++++++++++ 3 files changed, 507 insertions(+), 2 deletions(-) create mode 100644 src/FSharp.Core/immutablearray.fs diff --git a/src/FSharp.Core/FSharp.Core.fsproj b/src/FSharp.Core/FSharp.Core.fsproj index cf267f6f914..db9075e4913 100644 --- a/src/FSharp.Core/FSharp.Core.fsproj +++ b/src/FSharp.Core/FSharp.Core.fsproj @@ -50,6 +50,14 @@ $(ArtifactsDir)obj/$(MSBuildProjectName)/$(Configuration)/ + + + + + + + + true @@ -138,6 +146,9 @@ Collections/array3.fs + + Collections/immutablearray.fs + Collections/map.fsi diff --git a/src/FSharp.Core/FSharp.Core.nuspec b/src/FSharp.Core/FSharp.Core.nuspec index cc7e5316ac6..34dda8708ad 100644 --- a/src/FSharp.Core/FSharp.Core.nuspec +++ b/src/FSharp.Core/FSharp.Core.nuspec @@ -4,8 +4,12 @@ $CommonMetadataElements$ en-US - - + + + + + + diff --git a/src/FSharp.Core/immutablearray.fs b/src/FSharp.Core/immutablearray.fs new file mode 100644 index 00000000000..8a4868fb95a --- /dev/null +++ b/src/FSharp.Core/immutablearray.fs @@ -0,0 +1,490 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.FSharp.Collections + +open System +open System.Collections.Generic +open System.Collections.Immutable + +open Microsoft.FSharp.Core +open Microsoft.FSharp.Core.CompilerServices +open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators + +[] +[] +module ImmutableArray = + + [] + let inline length (array: ImmutableArray<'T>) : int = + array.Length + + [] + let inline singleton (item: 'T) : ImmutableArray<_> = + ImmutableArray.Create(item) + + [] + let inline init n ([] f: int -> 'T) : ImmutableArray<_> = + match n with + | 0 -> ImmutableArray.Empty + | 1 -> ImmutableArray.Create(f 0) + | n -> + if n < 0 then + invalidArg "n" "Below zero." + + let builder = ImmutableArray.CreateBuilder(n) + for i = 0 to n - 1 do + builder.Add(f i) + builder.MoveToImmutable() + + [] + let inline zeroCreate (count: int) : ImmutableArray<'T> = + if count < 0 then + invalidArgInputMustBeNonNegative (nameof(count)) count + + init count (fun _ -> LanguagePrimitives.GenericZero) + + [] + let inline create (count: int) (value: 'T) : ImmutableArray<'T> = + if count < 0 then + invalidArgInputMustBeNonNegative (nameof(count)) count + + init count (fun _ -> value) + + [] + let inline isEmpty (arr: ImmutableArray<_>) = arr.IsEmpty + + [] + [] + let empty<'T> = ImmutableArray<'T>.Empty + + let inline concatImmutableArrays (arrs: ImmutableArray>) : ImmutableArray<'T> = + match arrs.Length with + | 0 -> ImmutableArray.Empty + | 1 -> arrs[0] + | 2 -> arrs[0].AddRange(arrs[1]) + | n -> + let mutable acc = 0 + + for h in arrs do + acc <- acc + h.Length + + let builder = ImmutableArray.CreateBuilder(acc) + + for i = 0 to n - 1 do + builder.AddRange(arrs[i]) + + builder.MoveToImmutable() + + [] + let inline concat (arrs: IEnumerable>) : ImmutableArray<'T> = + match arrs with + | :? ImmutableArray> as arrs -> concatImmutableArrays arrs + | arrs -> concatImmutableArrays (ImmutableArray.CreateRange(arrs)) + + [] + let inline replicate (count: int) (value: 'T) : ImmutableArray<'T> = + create count value + + [] + let inline map ([] mapper: 'T -> 'U) (arr: ImmutableArray<'T>) : ImmutableArray<_> = + match arr.Length with + | 0 -> ImmutableArray.Empty + | 1 -> ImmutableArray.Create(mapper arr[0]) + | _ -> + let builder = ImmutableArray.CreateBuilder(arr.Length) + for i = 0 to arr.Length - 1 do + builder.Add(mapper arr[i]) + builder.MoveToImmutable() + + [] + let inline mapi ([] mapper: int -> 'T -> 'U) (arr: ImmutableArray<'T>) : ImmutableArray<_> = + match arr.Length with + | 0 -> ImmutableArray.Empty + | 1 -> ImmutableArray.Create(mapper 0 arr[0]) + | n -> + let builder = ImmutableArray.CreateBuilder(n) + for i = 0 to arr.Length - 1 do + builder.Add(mapper i arr[i]) + builder.MoveToImmutable() + + [] + let map2 (mapper: 'T1 -> 'T2 -> 'T) (array1: ImmutableArray<'T1>) (array2: ImmutableArray<'T2>) : ImmutableArray<_> = + if array1.Length <> array2.Length then + invalidArgDifferentArrayLength (nameof(array1)) array1.Length (nameof(array2)) array2.Length + + match array1.Length with + | 0 -> ImmutableArray.Empty + | 1 -> ImmutableArray.Create(mapper array1[0] array2[0]) + | n -> + let builder = ImmutableArray.CreateBuilder(n) + for i = 0 to n - 1 do + builder.Add(mapper array1[i] array2[i]) + builder.MoveToImmutable() + + [] + let mapi2 (mapper: int -> 'T1 -> 'T2 -> 'T) (array1: ImmutableArray<'T1>) (array2: ImmutableArray<'T2>) : ImmutableArray<_> = + if array1.Length <> array2.Length then + invalidArgDifferentArrayLength (nameof(array1)) array1.Length (nameof(array2)) array2.Length + + match array1.Length with + | 0 -> ImmutableArray.Empty + | 1 -> ImmutableArray.Create(mapper 0 array1[0] array2[0]) + | n -> + let builder = ImmutableArray.CreateBuilder(n) + for i = 0 to n - 1 do + builder.Add(mapper i array1[i] array2[i]) + builder.MoveToImmutable() + + [] + let collect (mapper: 'T -> ImmutableArray<'U>) (array: ImmutableArray<'T>) : ImmutableArray<'U> = + let result = map mapper array + concatImmutableArrays result + + [] + let take count (array: ImmutableArray<'T>) : ImmutableArray<'T> = + if count < 0 then + invalidArgInputMustBeNonNegative (nameof(count)) count + elif count = 0 || array.Length = 0 then + empty + elif count > array.Length then + raise (InvalidOperationException(SR.GetString(SR.notEnoughElements))) + else + let builder = ImmutableArray.CreateBuilder(count) + builder.AddRange(array, count) + builder.MoveToImmutable(); + + [] + let inline takeWhile ([] predicate) (array: ImmutableArray<'T>) : ImmutableArray<'T> = + let len = array.Length + if len = 0 then + empty + else + let mutable count = 0 + + let builder = ImmutableArray.CreateBuilder(len) + + while count < len && predicate array[count] do + count <- count + 1 + builder.Add(array[count]) + + builder.Capacity <- count + builder.Count <- count + builder.MoveToImmutable() + + let inline countByImpl + (comparer: IEqualityComparer<'SafeKey>) + ([] projection: 'T -> 'SafeKey) + ([] getKey: 'SafeKey -> 'Key) + (array: ImmutableArray<'T>) + = + + let length = array.Length + + if length = 0 then + Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked 0 + else + + let dict = Dictionary comparer + + // Build the groupings + for v in array do + let safeKey = projection v + let mutable prev = Unchecked.defaultof<_> + + if dict.TryGetValue(safeKey, &prev) then + dict[safeKey] <- prev + 1 + else + dict[safeKey] <- 1 + + let res = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked dict.Count + let mutable i = 0 + + for group in dict do + res[i] <- getKey group.Key, group.Value + i <- i + 1 + + res + + // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance + let inline countByValueType ([] projection: 'T -> 'Key) (array: ImmutableArray<'T>) = + countByImpl HashIdentity.Structural<'Key> projection id array + + // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation + let countByRefType (projection: 'T -> 'Key) (array: ImmutableArray<'T>) = + countByImpl + RuntimeHelpers.StructBox<'Key>.Comparer + (fun t -> RuntimeHelpers.StructBox(projection t)) + (fun sb -> sb.Value) + array + + [] + let countBy (projection: 'T -> 'Key) (array: ImmutableArray<'T>) = + // We return regular array here, since doesn't make much sense to return an immutable array of tuples. + if typeof<'Key>.IsValueType then + countByValueType projection array + else + countByRefType projection array + + [] + let inline append (array1: ImmutableArray<'T1>) (array2: ImmutableArray<'T1>) : ImmutableArray<_> = + array1.AddRange(array2) + + [] + let inline iter ([] f) (arr: ImmutableArray<'T>) = + for i = 0 to arr.Length - 1 do + f arr[i] + + [] + let inline iteri ([] f) (arr: ImmutableArray<'T>) = + for i = 0 to arr.Length - 1 do + f i arr[i] + + [] + let iter2 f (array1: ImmutableArray<'T1>) (array2: ImmutableArray<'T2>) = + if array1.Length <> array2.Length then + invalidArgDifferentArrayLength (nameof(array1)) array1.Length (nameof(array2)) array2.Length + + for i = 0 to array1.Length - 1 do + f array1[i] array2[i] + + [] + let iteri2 f (array1: ImmutableArray<'T1>) (array2: ImmutableArray<'T2>) = + if array1.Length <> array2.Length then + invalidArgDifferentArrayLength (nameof(array1)) array1.Length (nameof(array2)) array2.Length + + for i = 0 to array1.Length - 1 do + f i array1[i] array2[i] + + + [] + let inline forall ([] predicate) (arr: ImmutableArray<'T>) : bool = + let len = arr.Length + let rec loop i = + i >= len || (predicate arr[i] && loop (i+1)) + loop 0 + + [] + let forall2 predicate (array1: ImmutableArray<'T1>) (array2: ImmutableArray<'T2>) : bool = + if array1.Length <> array2.Length then + invalidArgDifferentArrayLength (nameof(array1)) array1.Length (nameof(array2)) array2.Length + + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(predicate) + + let len1 = array1.Length + let rec loop i = + i >= len1 || (f.Invoke(array1[i], array2[i]) && loop (i+1)) + loop 0 + + [] + let inline tryFind ([] predicate) (arr: ImmutableArray<'T>) = + let len = arr.Length + + let rec loop i = + if i >= len then + None + elif predicate arr[i] then + Some arr[i] + else + loop (i+1) + + if len = 0 then + None + else + loop 0 + + [] + let inline tryFindIndex ([] predicate) (arr: ImmutableArray<'T>) = + let len = arr.Length + + let rec go n = + if n >= len then + None + elif predicate arr[n] then + Some n + else go (n+1) + + if len = 0 then + None + else + go 0 + + [] + let inline tryPick ([] chooser) (arr: ImmutableArray<'T>) = + let len = arr.Length + let rec loop i = + if i >= len then + None + else + match chooser arr[i] with + | None -> loop(i+1) + | res -> res + + if len = 0 then + None + else + loop 0 + + [] + let inline head (array: ImmutableArray<'T>) : 'T = + if array.Length = 0 then + invalidArg (nameof(array)) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + else + array[0] + + [] + let inline tryHead (array: ImmutableArray<'T>) : 'T option = + if array.Length = 0 then + None + else + Some array[0] + + [] + let tail (array: ImmutableArray<'T>) : ImmutableArray<'T> = + // Since we target ns2.0 & ns2.1, we cannot use Slice method here. + let len = array.Length + + if len = 0 then + invalidArg (nameof(array)) (SR.GetString(SR.notEnoughElements)) + elif len = 1 then + singleton array[0] + else + let builder = ImmutableArray.CreateBuilder(len - 1) + + for i = 1 to len - 1 do + builder.Add(array[i]) + + builder.MoveToImmutable() + + [] + let inline last (array: ImmutableArray<'T>) : 'T = + if array.Length = 0 then + invalidArg (nameof(array)) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + + array[array.Length - 1] + + [] + let inline tryLast (array: ImmutableArray<'T>) : 'T option = + if array.Length = 0 then + None + else + Some array[array.Length - 1] + + [] + let splitAt index (array: ImmutableArray<'T>) = + + if index < 0 then + invalidArgInputMustBeNonNegative "index" index + + let len = array.Length + + if len < index then + raise <| InvalidOperationException(SR.GetString(SR.notEnoughElements)) + + if index = 0 then + empty, array + elif index = len then + array, empty + else + let builderLeft = ImmutableArray.CreateBuilder(index) + let builderRight = ImmutableArray.CreateBuilder(len - index) + + for i = 0 to len do + if i < index then + builderLeft.Add(array[i]) + else + builderRight.Add(array[i]) + + builderLeft.MoveToImmutable(), builderRight.MoveToImmutable() + + [] + let inline toArray (array: ImmutableArray<'T>) : 'T array = + let len = array.Length + let res = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked len + for i = 0 to len - 1 do + res[i] <- array[i] + res + + [] + let inline ofArray (list: 'T array) : ImmutableArray<'T> = + ImmutableArray.CreateRange list + + [] + let inline ofSeq (xs: IEnumerable<'T>) = + ImmutableArray.CreateRange(xs) + + [] + let inline toList (array: ImmutableArray<'T>) : 'T list = + List.ofSeq array + + [] + let inline ofList (list: 'T list) : ImmutableArray<'T> = + ImmutableArray.CreateRange list + + [] + let inline filter ([] predicate) (arr: ImmutableArray<'T>) : ImmutableArray<'T> = + let len = arr.Length + let builder = ImmutableArray.CreateBuilder(len) + for i = 0 to len - 1 do + if predicate arr[i] then + builder.Add(arr[i]) + builder.Capacity <- builder.Count + builder.MoveToImmutable() + + [] + let inline exists ([] predicate) (arr: ImmutableArray<'T>) : bool = + let len = arr.Length + let rec loop i = + i < len && (predicate arr[i] || loop (i+1)) + len > 0 && loop 0 + + [] + let inline choose ([] chooser: 'T -> 'U option) (arr: ImmutableArray<'T>) : ImmutableArray<'U> = + let len = arr.Length + let builder = ImmutableArray.CreateBuilder(len) + for i = 0 to len - 1 do + let result = chooser arr[i] + if result.IsSome then + builder.Add(result.Value) + builder.Capacity <- builder.Count + builder.MoveToImmutable() + + [] + let inline distinct (array: ImmutableArray<'T>) : ImmutableArray<'T> = + let temp = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length + let mutable i = 0 + + let hashSet = HashSet<'T>(HashIdentity.Structural<'T>) + + for v in array do + if hashSet.Add(v) then + temp[i] <- v + i <- i + 1 + + ImmutableArray.CreateRange(temp) + + [] + let inline distinctBy projection (array: ImmutableArray<'T>) : ImmutableArray<'T> = + let length = array.Length + + if length = 0 then + empty + else + + let temp = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length + let mutable i = 0 + let hashSet = HashSet<_>(HashIdentity.Structural<_>) + + for v in array do + if hashSet.Add(projection v) then + temp[i] <- v + i <- i + 1 + + ImmutableArray.CreateRange(temp) + + [] + let inline fold ([] folder) state (arr: ImmutableArray<_>) = + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(folder) + let mutable state = state + for i = 0 to arr.Length - 1 do + state <- f.Invoke(state, arr[i]) + state