Skip to content

Fsharp.Core :: {Array;List;Set;Array.Parallel} partitionWith (taking Choice<T,U> partitioner)#19335

Merged
T-Gro merged 18 commits intomainfrom
feature-fsharpcore-partitionMap
Feb 26, 2026
Merged

Fsharp.Core :: {Array;List;Set;Array.Parallel} partitionWith (taking Choice<T,U> partitioner)#19335
T-Gro merged 18 commits intomainfrom
feature-fsharpcore-partitionMap

Conversation

@T-Gro
Copy link
Member

@T-Gro T-Gro commented Feb 20, 2026

Implements fsharp/fslang-suggestions#1119

Signatures

module Array =
    val inline partitionWith: partitioner: ('T -> Choice<'T1, 'T2>) -> array: 'T array -> 'T1 array * 'T2 array

module Array.Parallel =
    val inline partitionWith: partitioner: ('T -> Choice<'T1, 'T2>) -> array: 'T array -> 'T1 array * 'T2 array

module List =
    val inline partitionWith: partitioner: ('T -> Choice<'T1, 'T2>) -> list: 'T list -> 'T1 list * 'T2 list

module Set =
    val partitionWith: partitioner: ('T -> Choice<'T1, 'T2>) -> set: Set<'T> -> Set<'T1> * Set<'T2>

Example — active patterns

A total active pattern (|A|B|) is already 'T -> Choice<'T1, 'T2> — pass it directly.

let (|Valid|Invalid|) (s: string) =
    match System.Int32.TryParse s with
    | true, n -> Valid n
    | _ -> Invalid s

let parsed, errors =
    ["42"; "hello"; "7"; "oops"; "99"]
    |> List.partitionWith (|Valid|Invalid|)

// parsed = [42; 7; 99]
// errors = ["hello"; "oops"]

T-Gro and others added 16 commits February 13, 2026 22:54
Adds List.partitionWith that splits a list into two lists of potentially
different types using a Choice-returning partitioner function.

- Implementation in list.fs using ListCollector and recursive loop
- Signature with XML docs and example in list.fsi
- Unit tests covering basic, empty, all-Choice1, all-Choice2, single
  element, and order preservation cases
Adds Array.partitionWith function that splits an array into two arrays
by applying a partitioner function returning Choice<'T1,'T2>.
Uses ArrayCollector<'T> for efficient array building.

Includes XML documentation with example and unit tests covering:
- Basic type-changing partition
- Empty array input
- All Choice1Of2 / all Choice2Of2
- Single element
- Order preservation
- Null input throws ArgumentNullException
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- List: stack safety test with 100k elements verifying tail recursion
- List/Array/Set: exception propagation tests with InvalidOperationException

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tests

Replace Assert.Throws<InvalidOperationException> with the existing
CheckThrowsInvalidOperationExn helper from LibraryTestFx.fs for
consistency with the rest of the test codebase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds FsCheck property-based consistency tests to
CollectionModulesConsistency.fs:
- List vs Array: verifies identical ordered results across int,
  string, NormalFloat
- Set vs Array: verifies set-equal results (order-independent)
  across int, string, NormalFloat

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace iter-based ascending traversal with right→key→left recursive
traversal, matching partitionAux's proven strategy. Elements are inserted
largest-first into output trees, causing fewer rebalancing rotations.
Internal accumulator uses struct tuples to avoid per-step allocations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace ChunkedArrayCollector with a two-pass strategy:
- Pass 1: call partitioner, store results, count Choice1Of2
- Pass 2: allocate exact-sized arrays, scatter results

Mark function inline with [<InlineIfLambda>] on partitioner parameter.
Remove ChunkedArrayCollector type (only used by partitionWith).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite body to use ListCollector instead of freshConsNoTail/setFreshConsTail
to avoid FieldAccessException when inlined into external assemblies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implement the parallel version of Array.partitionWith using the same
Parallel.For + thread-local accumulator + Interlocked.Add pattern as
Array.Parallel.choose.

- Implementation in src/FSharp.Core/array.fs
- Signature with XML docs in src/FSharp.Core/array.fsi
- Unit tests and consistency tests
- Surface area baselines updated (4 .bsl files)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ase notes

- Replace intermediate Choice<'T1,'T2>[] in Array.partitionWith with the
  3-array pattern (bool[] + T1[] + T2[]) matching the parallel version,
  eliminating N per-element heap allocations of Choice DU objects
- Add Array.Parallel.partitionWith to release notes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

❗ Release notes required


✅ Found changes and release notes in following paths:

Warning

No PR link found in some release notes, please consider adding it.

Change path Release notes path Description
src/FSharp.Core docs/release-notes/.FSharp.Core/10.0.300.md No current pull request URL (#19335) found, please consider adding it

@Happypig375
Copy link
Member

No Choice<'a, 'b, 'c> or Choice<'a, 'b, 'c, 'd> versions?

@T-Gro
Copy link
Member Author

T-Gro commented Feb 20, 2026

No Choice<'a, 'b, 'c> or Choice<'a, 'b, 'c, 'd> versions?

fsharp/fslang-suggestions#1119 (comment)

But your question made me realize that if we wanted to support all various sorts (all the 7 choices, and result,..), it could be solved by a language-level facility that inverts collection of N-case DU items, and puts them into N different collections of respective fields of those cases.

Maybe more on the "type provider generate types+members from existing types" side ... ?

@T-Gro T-Gro marked this pull request as ready for review February 20, 2026 10:31
@T-Gro T-Gro requested a review from a team as a code owner February 20, 2026 10:31
@T-Gro T-Gro requested a review from abonie February 26, 2026 13:52
@T-Gro T-Gro enabled auto-merge (squash) February 26, 2026 13:52
@github-project-automation github-project-automation bot moved this from New to In Progress in F# Compiler and Tooling Feb 26, 2026
The new Array.partitionWith and Array.Parallel.partitionWith methods
produce StackUnexpected ILVerify errors for Bool/Byte types on the stack,
matching the existing pattern for Array.Parallel.Choose/Filter/Partition.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro merged commit 657fc0d into main Feb 26, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants