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