Repository navigation
refactor: split Helper.fs into root modules and declare InternalsVisibleTo in the project file
#40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
3bd256d
refactor: split Helper.fs into root modules
xperiandri c633a79
chore: give the tests access to internals through the project file
xperiandri 975eda4
refactor: name the resource of each text in `ErrorStrings` and drop t…
xperiandri 0b4024b
build: generate the InternalsVisibleTo attribute with the properties …
xperiandri 5dbc87d
refactor: read IsDefault through a piped helper
xperiandri 3aa7f18
refactor: build the messages of the Raise module with interpolated st…
xperiandri 7691911
perf: reject a class before reading the generic definition in isDefau…
xperiandri 5815d56
docs: say why each algorithm of Arrays exists
xperiandri 0e3d522
docs: start the comments of Arrays with the summaries of the FSharp.C…
xperiandri 0685f2b
build: drop the properties that switch off the assembly attributes of…
xperiandri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /// The checks of the FSharp.Core Array module that the functions of several arrays share. | ||
| module internal FSharp.Collections.Immutable.ArrayChecks | ||
|
|
||
| /// The arrays of a function of two arrays must have the same length. | ||
| let inline sameLengths (name1 : string) (array1 : 'T1 array) (name2 : string) (array2 : 'T2 array) = | ||
| if array1.Length <> array2.Length then | ||
| Raise.invalidArgDifferentArrayLength name1 array1.Length name2 array2.Length | ||
|
|
||
| /// The arrays of a function of three arrays must have the same length. | ||
| let inline sameLengths3 | ||
| (name1 : string) | ||
| (array1 : 'T1 array) | ||
| (name2 : string) | ||
| (array2 : 'T2 array) | ||
| (name3 : string) | ||
| (array3 : 'T3 array) | ||
| = | ||
| if | ||
| array1.Length <> array2.Length | ||
| || array1.Length <> array3.Length | ||
| then | ||
| Raise.invalidArg3ArraysDifferent name1 name2 name3 array1.Length array2.Length array3.Length |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| /// Algorithms over arrays that the default modules and the `Tuple` modules share (ADR 0001, D3). | ||
| /// Each algorithm takes the constructor of the tuples that it produces, so that neither variant pays for a second pass. | ||
| /// The callers check their arguments first, with the exceptions of the FSharp.Core module that they mirror. | ||
| /// The algorithms exist because the functions of the FSharp.Core `Array` module that build or take pairs and triples work | ||
| /// with reference tuples. The default functions of `FlatList` return struct tuples, and the functions of the `Tuple` | ||
| /// namespace return reference tuples. Calling the `Array` function and converting its result would allocate every | ||
| /// reference tuple and a second array. Each algorithm has exactly two callers, the function of `FlatList` with | ||
| /// `structPair` or `structTriple` and the function of `Tuple.FlatList` with `referencePair` or `referenceTriple`, so | ||
| /// that it exists once. For 10 000 elements, `zip` of struct tuples allocates 80 KB where `Array.zip` followed by a | ||
| /// conversion allocates 400 KB. | ||
| /// The first sentences of the comment of each function are the summary of the function of the FSharp.Core `Array` module | ||
| /// word for word (FSharp.Core 10.1), and the sentences after them say what differs and why the function exists. | ||
| module internal FSharp.Collections.Immutable.Arrays | ||
|
|
||
| open System | ||
| open System.Collections.Generic | ||
| open System.Linq | ||
|
|
||
| /// Returns a new array that contains all pairings of elements from the first and second arrays. | ||
| /// The pairs are built with `tuple`. It exists because `Array.allPairs` returns reference tuples. `FlatList.allPairs` | ||
| /// builds struct tuples with it directly (720 KB for 300 by 300 elements, where `Array.allPairs` followed by a conversion | ||
| /// allocates 3.6 MB). | ||
| let inline allPairs ([<InlineIfLambda>] tuple : 'T1 -> 'T2 -> 'R) (array1 : 'T1 array) (array2 : 'T2 array) : 'R array = | ||
| let length2 = array2.Length | ||
| let result = Array.zeroCreate (array1.Length * length2) | ||
|
|
||
| for i = 0 to array1.Length - 1 do | ||
| for j = 0 to length2 - 1 do | ||
| result[i * length2 + j] <- tuple array1[i] array2[j] | ||
|
|
||
| result | ||
|
|
||
| /// A key that may be null, for a dictionary, which rejects null keys. FSharp.Core has no such type in its public API. | ||
| /// It exists because `Array.countBy` and `Array.groupBy` accept a null key (`[| null; "a"; null |]` has two keys), and | ||
| /// `Dictionary` throws `ArgumentNullException` for one. | ||
| [<Struct>] | ||
| type NullableKey<'Key> = { Key : 'Key } | ||
|
|
||
| /// Compares wrapped keys in F# structural equality, which handles null keys, as FSharp.Core does for its keys. | ||
| /// It exists because the default comparer of a `Dictionary` compares array keys by reference, where `Array.countBy` | ||
| /// finds `[| 1 |]` and `[| 1 |]` equal. | ||
| let nullableKeyComparer<'Key when 'Key : equality> () = | ||
| let structural = HashIdentity.Structural<'Key> | ||
|
|
||
| { | ||
| new IEqualityComparer<NullableKey<'Key>> with | ||
| member _.Equals (left, right) = structural.Equals (left.Key, right.Key) | ||
| member _.GetHashCode key = structural.GetHashCode key.Key | ||
| } | ||
|
|
||
| /// Applies a key-generating function to each element of an array and returns an array yielding unique keys and their | ||
| /// number of occurrences in the original array. | ||
| /// The unique keys come in the order of their first occurrence, and the counting keeps one counter per key, not the | ||
| /// elements, so it needs memory for the keys only. The tuples are built with `tuple`. It exists because `Array.countBy` | ||
| /// returns reference tuples; null keys and keys that are arrays work as in FSharp.Core. | ||
| let inline countBy | ||
| ([<InlineIfLambda>] tuple : 'Key -> int -> 'R) | ||
| (projection : 'T -> 'Key) | ||
| (array : 'T array) | ||
| : 'R array when 'Key : equality = | ||
| let indices = Dictionary<NullableKey<'Key>, int>(nullableKeyComparer<'Key>()) | ||
| let keys = ResizeArray<'Key>() | ||
| let counts = ResizeArray<int>() | ||
|
|
||
| for item in array do | ||
| let key = projection item | ||
| let mutable index = 0 | ||
|
|
||
| if indices.TryGetValue ({ Key = key }, &index) then | ||
| counts[index] <- counts[index] + 1 | ||
| else | ||
| indices.Add ({ Key = key }, keys.Count) | ||
| keys.Add key | ||
| counts.Add 1 | ||
|
|
||
| let result = Array.zeroCreate keys.Count | ||
|
|
||
| for index = 0 to result.Length - 1 do | ||
| result[index] <- tuple keys[index] counts[index] | ||
|
|
||
| result | ||
|
|
||
| /// Applies a key-generating function to each element of an array and yields an array of unique keys. Each unique key | ||
| /// contains an array of all elements that match to this key. | ||
| /// The keys come in the order of their first occurrence, and the elements of a key in their order. The tuples are built | ||
| /// with `tuple`. It exists because `Array.groupBy` returns reference tuples. It groups once with a lookup, as the project | ||
| /// rules ask. | ||
| let inline groupBy | ||
| ([<InlineIfLambda>] tuple : 'Key -> 'T array -> 'R) | ||
| (projection : 'T -> 'Key) | ||
| (array : 'T array) | ||
| : 'R array when 'Key : equality = | ||
| let lookup = array.ToLookup ((fun item -> projection item), HashIdentity.Structural<'Key>) | ||
| let result = Array.zeroCreate lookup.Count | ||
| let mutable index = 0 | ||
|
|
||
| for grouping in lookup do | ||
| result[index] <- tuple grouping.Key (grouping.ToArray ()) | ||
| index <- index + 1 | ||
|
|
||
| result | ||
|
|
||
| /// Builds a new array whose elements are the corresponding elements of the input array paired with the integer index | ||
| /// (from 0) of each element. | ||
| /// The pairs are built with `tuple`. It exists because `Array.indexed` returns reference tuples (320 KB for 10 000 | ||
| /// elements, where struct tuples take 80 KB). | ||
| let inline indexed ([<InlineIfLambda>] tuple : int -> 'T -> 'R) (array : 'T array) : 'R array = | ||
| let result = Array.zeroCreate array.Length | ||
|
|
||
| for i = 0 to result.Length - 1 do | ||
| result[i] <- tuple i array[i] | ||
|
|
||
| result | ||
|
|
||
| /// Returns an array of each element in the input array paired with its predecessor, with the exception of the first | ||
| /// element which is only returned as the predecessor of the second element. The predecessor comes first in the returned | ||
| /// pairs. | ||
| /// The pairs are built with `tuple`. It exists because `Array.pairwise` returns reference tuples. | ||
| let inline pairwise ([<InlineIfLambda>] tuple : 'T -> 'T -> 'R) (array : 'T array) : 'R array = | ||
| if array.Length < 2 then | ||
| Array.Empty () | ||
| else | ||
| let result = Array.zeroCreate (array.Length - 1) | ||
|
|
||
| for i = 0 to result.Length - 1 do | ||
| result[i] <- tuple array[i] array[i + 1] | ||
|
|
||
| result | ||
|
|
||
| /// Splits an array of pairs into two arrays. | ||
| /// `split` reads a pair as a struct tuple. It exists because `Array.unzip` takes reference tuples. `FlatList.unzip` has | ||
| /// struct tuples and passes `id` as `split`, `Tuple.FlatList.unzip` has reference tuples and converts one pair at a time, | ||
| /// so that no array of converted pairs (400 KB for 10 000 pairs) is built. | ||
| let inline unzip ([<InlineIfLambda>] split : 'P -> struct ('T1 * 'T2)) (array : 'P array) = | ||
| let result1 = Array.zeroCreate array.Length | ||
| let result2 = Array.zeroCreate array.Length | ||
|
|
||
| for i = 0 to array.Length - 1 do | ||
| let struct (first, second) = split array[i] | ||
| result1[i] <- first | ||
| result2[i] <- second | ||
|
|
||
| struct (result1, result2) | ||
|
|
||
| /// Splits an array of triples into three arrays. | ||
| /// `split` reads a triple as a struct tuple. It exists for the reason of `unzip`: `Array.unzip3` takes reference tuples. | ||
| let inline unzip3 ([<InlineIfLambda>] split : 'P -> struct ('T1 * 'T2 * 'T3)) (array : 'P array) = | ||
| let result1 = Array.zeroCreate array.Length | ||
| let result2 = Array.zeroCreate array.Length | ||
| let result3 = Array.zeroCreate array.Length | ||
|
|
||
| for i = 0 to array.Length - 1 do | ||
| let struct (first, second, third) = split array[i] | ||
| result1[i] <- first | ||
| result2[i] <- second | ||
| result3[i] <- third | ||
|
|
||
| struct (result1, result2, result3) | ||
|
|
||
| /// Combines the two arrays into an array of pairs. The two arrays must have equal lengths, otherwise an ArgumentException | ||
| /// is raised. | ||
| /// The pairs are built with `tuple`, and the callers check the lengths first and raise the exception of `Array.zip`. It | ||
| /// exists because `Array.zip` returns reference tuples (400 KB for 10 000 elements with the conversion, 80 KB here). | ||
| let inline zip ([<InlineIfLambda>] tuple : 'T1 -> 'T2 -> 'R) (array1 : 'T1 array) (array2 : 'T2 array) : 'R array = | ||
| let result = Array.zeroCreate array1.Length | ||
|
|
||
| for i = 0 to result.Length - 1 do | ||
| result[i] <- tuple array1[i] array2[i] | ||
|
|
||
| result | ||
|
|
||
| /// Combines three arrays into an array of triples. The three arrays must have equal lengths, otherwise an | ||
| /// ArgumentException is raised. (The summary of `Array.zip3` says "an array of pairs", which is a slip.) | ||
| /// The triples are built with `tuple`, and the callers check the lengths first. It exists for the reason of `zip`: | ||
| /// `Array.zip3` returns reference tuples. | ||
| let inline zip3 | ||
| ([<InlineIfLambda>] tuple : 'T1 -> 'T2 -> 'T3 -> 'R) | ||
| (array1 : 'T1 array) | ||
| (array2 : 'T2 array) | ||
| (array3 : 'T3 array) | ||
| : 'R array = | ||
| let result = Array.zeroCreate array1.Length | ||
|
|
||
| for i = 0 to result.Length - 1 do | ||
| result[i] <- tuple array1[i] array2[i] array3[i] | ||
|
|
||
| result |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| /// The default comparers of the collections (ADR 0001, D5). | ||
| [<AutoOpen>] | ||
| module internal FSharp.Collections.Immutable.Comparers | ||
|
|
||
| open System.Collections.Generic | ||
|
|
||
| /// <summary> | ||
| /// The comparer of F# generic comparison for a type: the comparer of the sorts, of <c>binarySearch</c> and of the | ||
| /// sorted collections that are created without a comparer. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// The class resolves <see cref="M:Microsoft.FSharp.Core.LanguagePrimitives.FastGenericComparer``1"/> once per type, in | ||
| /// generic code, where FSharp.Core returns the comparer that it keeps for the type. | ||
| /// </para> | ||
| /// <para> | ||
| /// A call of <c>FastGenericComparer</c> at a concrete type that is not primitive creates a new comparer each time, and | ||
| /// the F# optimizer creates such calls when it inlines a small function of the library into its caller. Collections of | ||
| /// one type would then have different comparers, and the BCL could not take its shortcuts for two collections with the | ||
| /// same comparer, such as the one of <c>ImmutableSortedSet.Union</c>. A static field cannot be inlined. | ||
| /// </para> | ||
| /// </remarks> | ||
| [<Sealed>] | ||
| type DefaultComparer<'T when 'T : comparison> private () = | ||
|
|
||
| static let comparer = LanguagePrimitives.FastGenericComparer<'T> | ||
|
|
||
| /// The comparer of F# generic comparison for the type. | ||
| static member Instance : IComparer<'T> = comparer |
|
xperiandri marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /// <summary> | ||
| /// The exception messages of FSharp.Core (<c>FSCore.resx</c>) and of the .NET libraries, in English. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The modules throw what the FSharp.Core function that they mirror throws (ADR 0001, D6). FSharp.Core keeps its resources | ||
| /// internal, so the texts are copied. The texts of the .NET libraries are for the functions that report a BCL exception | ||
| /// under the name of their own parameter. Each constant is the text of one resource and its comment names the resource, | ||
| /// so a text that no resource of FSharp.Core or of the .NET libraries holds does not belong here. | ||
| /// </remarks> | ||
| module internal FSharp.Collections.Immutable.ErrorStrings | ||
|
|
||
| /// FSCore.resx, arraysHadDifferentLengths. | ||
| [<Literal>] | ||
| let ArraysHadDifferentLengths = "The arrays have different lengths." | ||
|
|
||
| /// FSCore.resx, indexOutOfBounds. | ||
| [<Literal>] | ||
| let IndexOutOfBounds = "The index was outside the range of elements in the list." | ||
|
|
||
| /// FSCore.resx, arrayWasEmpty. | ||
| [<Literal>] | ||
| let InputArrayEmpty = "The input array was empty." | ||
|
|
||
| /// FSCore.resx, inputListWasEmpty. | ||
| [<Literal>] | ||
| let InputListWasEmpty = "The input list was empty." | ||
|
|
||
| /// FSCore.resx, inputMustBeNonNegative. | ||
| [<Literal>] | ||
| let InputMustBeNonNegative = "The input must be non-negative." | ||
|
|
||
| /// FSCore.resx, inputMustBePositive. | ||
| [<Literal>] | ||
| let InputMustBePositive = "The input must be positive." | ||
|
|
||
| /// FSCore.resx, inputSequenceEmpty. | ||
| [<Literal>] | ||
| let InputSequenceEmpty = "The input sequence was empty." | ||
|
|
||
| /// FSCore.resx, inputSequenceTooLong. | ||
| [<Literal>] | ||
| let InputSequenceTooLong = "The input sequence contains more than one element." | ||
|
|
||
| /// FSCore.resx, keyNotFound. | ||
| [<Literal>] | ||
| let KeyNotFound = "The item, key, or index was not found in the collection." | ||
|
|
||
| /// FSCore.resx, keyNotFoundAlt. | ||
| [<Literal>] | ||
| let KeyNotFoundAlt = "An index satisfying the predicate was not found in the collection." | ||
|
|
||
| /// FSCore.resx, listsHadDifferentLengths. | ||
| [<Literal>] | ||
| let ListsHadDifferentLengths = "The lists had different lengths." | ||
|
|
||
| /// System.Private.CoreLib, ArgumentOutOfRange_NeedNonNegNum: the message of the .NET libraries for a negative argument, as | ||
| /// `Array.BinarySearch` reports it. | ||
| [<Literal>] | ||
| let NeedNonNegativeNumber = "Non-negative number required." | ||
|
|
||
| /// FSCore.resx, notAPermutation. | ||
| [<Literal>] | ||
| let NotAPermutation = "The function did not compute a permutation." | ||
|
|
||
| /// FSCore.resx, notEnoughElements. | ||
| [<Literal>] | ||
| let NotEnoughElements = "The input sequence has an insufficient number of elements." | ||
|
|
||
| /// FSCore.resx, outOfRange. | ||
| [<Literal>] | ||
| let OutOfRange = "The index is outside the legal range." |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.