Skip to content

refactor: return voption and struct tuples from the collection functions - #47

Merged
xperiandri merged 2 commits into
mainfrom
refactor/voption-collections
Oct 10, 2026
Merged

xperiandri merged 2 commits into
mainfrom
refactor/voption-collections

Conversation

@xperiandri

@xperiandri xperiandri commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Proposed Changes

The repository prefers voption and struct tuples, but FSharp.Core's collection functions that look an element up return an option, and zip returns reference tuples. Every call then either allocates or needs a conversion at the call site. This was asked for in the review of #43 (helpers that return voption, zip over Enumerable.Zip).

src/Shared/ValueCollections.fs defines functions with the names and the meaning of FSharp.Core's, for Seq, List and Array:

  • tryHead, tryLast, tryItem, tryExactlyOne, tryFind, tryFindBack, tryFindIndex, tryFindIndexBack return a voption;
  • tryPick returns a voption and takes a chooser that returns one;
  • zip returns struct tuples. Seq.zip is Enumerable.Zip; Array.zip and List.zip still reject collections of different lengths, as FSharp.Core's do.

They shadow FSharp.Core's functions: Seq.tryHead now means this one. A function that only FSharp.Core has, such as Seq.map, resolves to FSharp.Core as before, and a shadowed one stays reachable by its full name (Microsoft.FSharp.Collections.Seq.tryHead).

How it reaches the code. The root Directory.Build.props adds the file as the first Compile item of every F# project under src and tests, so each project gets its own internal copy and a new project gets it without doing anything. The module is FSharp.Azure.Cosmos.ValueCollections and marked AutoOpen:

  • in the code of the namespace FSharp.Azure.Cosmos, which includes modules declared as module FSharp.Azure.Cosmos.X, the shadows are in effect without an open;
  • any other file gets them with open FSharp.Azure.Cosmos, which nearly every file of the library and of its tests has anyway;
  • that includes a file in a child namespace such as FSharp.Azure.Cosmos.Tests: a child namespace does not open the modules of its parent (tried out);
  • a file that does not open the namespace keeps FSharp.Core's functions.

The build project is left out: its scripts were generated against FSharp.Core's functions.

The two places on main that used such a function follow: the first item of a feed page in Read.fs and a zip in the ExistsAsync concurrency test.

Change after the review

The module was in the global namespace, so that the shadows were in effect in every file without an open. As asked in the review, it is in the namespace FSharp.Azure.Cosmos now. The one file here that needed the open for it is the test of the shadows; the other two places that use them are inside the namespace or open it already. The branch is also rebased onto the current main.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Internal refactoring; the public API of the package is unchanged.

Checklist

  • Build and tests pass locally
  • I have added tests that prove my fix is effective or that my feature works (if appropriate)
  • I have added necessary documentation (if appropriate)

Further comments

  • ValueCollectionsTests (5 tests, category ValueCollections, no emulator) runs every shadow and the FSharp.Core function it hides over the same inputs – empty, one element, repeated elements, a longer run; indexes before, inside and past the collection – and asserts equal answers.
  • Windows emulator: Debug 190/190 and Release 190/190. Fantomas is clean, and the documentation builds with --strict.
  • Not shadowed: choose and pick, whose choosers return an option as well, and Map.tryFind. They are not "try" functions of the three modules that the review named; say so if they should follow.
  • Of the open pull requests only test: add a Hedgehog adapter for MSTest property tests #43 uses these functions beyond the two places above; it is built on this branch, so this one has to be merged first.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 10, 2026 11:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The helpers preserve expected semantics, affected usages are updated, and the new behavior is comprehensively tested.

0 open findings

What changed in this PR

Introduces repository-wide collection helpers that return voption and struct tuples while preserving FSharp.Core behavior.

Changes:

  • Adds shadowing Seq, List, and Array helpers.
  • Updates affected call sites for new result types.
  • Adds comprehensive emulator-free tests and project guidance.
File Description
.github/​copilot-instructions.md Documents the shared helpers and conventions.
Directory.Build.props Includes helpers in applicable F# projects.
src/​Shared/​ValueCollections.fs Implements collection shadows.
src/​Cosmos/​Read.fs Handles the new voption result.
tests/​Cosmos.Tests/​FSharp.Azure.Cosmos.Tests.fsproj Includes the new tests.
tests/​Cosmos.Tests/​ReadExtensionsTests.fs Updates struct-tuple pattern matching.
tests/​Cosmos.Tests/​TestCategories.fs Adds the test category attribute.
tests/​Cosmos.Tests/​ValueCollectionsTests.fs Verifies behavior against FSharp.Core.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@xperiandri

Copy link
Copy Markdown
Collaborator Author

Should we stack it on top of Hedgehog and perform property testing? Or does not make sense?

@xperiandri

Copy link
Copy Markdown
Collaborator Author

Should we stack it on top of Hedgehog and perform property testing? Or does not make sense?

It makes sense, and it is done in #50. Stacking this pull request on the Hedgehog one is not possible, though, so the property tests come as a third step.

Why it makes sense. Every function here hides the FSharp.Core function of the same name and must mean the same. That gives each one an oracle: another implementation that knows the expected answer for any input. A test can then run both on generated input and compare, with no expected values written by hand. That is the case property tests suit best.

Why not by stacking. The dependency goes the other way: the adapter of #43 is written with these functions (its review asked for the repository's conventions in the ported code), so #43 is built on this branch. The order is #47, then #43, then #50.

What #50 does. It replaces the three test methods of this pull request that compare the shadows with FSharp.Core on five hand-picked arrays by four methods marked [<Property>]: first, last and only element; element at an index; the five search functions; zip. Each compares Seq, List and Array on the same generated input, 100 inputs per run.

Does it find more? I planted a defect that the hand-picked arrays do not reach: List.tryLast returning the element before the last one when that element is 3. The tests of this pull request pass with it, 5 of 5. The property fails after 24 generated cases and reports the smallest failing input, items = [|0; 3; 0|]. The defect was reverted afterwards; #50 describes the two other defects tried and what the generators produce.

Nothing changes in this pull request: its hand-picked tests stay until #50 replaces them.

xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; Odd and Even stay inside their range; DateOnly
and TimeOnly get generators, upstream through
hedgehogqa/fsharp-hedgehog#488. The open question of the adapter's code
style is answered, with formatting and nullness checking still open.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, Pascal-case
assertion functions and ImmutableArray for shared test data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The open question of the adapter's code
style is answered, with formatting and nullness checking still open.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, Pascal-case
assertion functions and ImmutableArray for shared test data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/Shared/ValueCollections.fs Outdated
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The open question of the adapter's code
style is answered, with formatting and nullness checking still open.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, Pascal-case
assertion functions and ImmutableArray for shared test data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the refactor/voption-collections branch from 0aa9491 to b62a75c Compare October 10, 2026 16:23
xperiandri and others added 2 commits October 10, 2026 18:58
FSharp.Core's Seq, List and Array functions that look an element up
return an option, and zip returns reference tuples: one heap object per
result. src/Shared/ValueCollections.fs shadows them with functions of
the same names and meaning that return a voption or struct tuples:
tryHead, tryLast, tryItem, tryExactlyOne, tryFind, tryFindBack,
tryFindIndex, tryFindIndexBack, tryPick, whose chooser returns a voption
too, and zip. Seq.zip is Enumerable.Zip.

Every F# project under src and tests compiles the file first, through
the root Directory.Build.props, and gets an internal copy. The module
opens itself in the namespace FSharp.Azure.Cosmos, so the shadows are in
effect in the code of that namespace and in every file that opens it,
while a function that only FSharp.Core has resolves to it as before. A
file in another namespace opens FSharp.Azure.Cosmos to get them, and so
does one in a child namespace such as FSharp.Azure.Cosmos.Tests: a child
namespace does not open the modules of its parent. The build project is
left out, because its scripts were generated against the functions of
FSharp.Core.

The two places that used such a function follow: the first item of a
feed page in Read.fs and a zip in the ExistsAsync concurrency test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The
open question of the adapter's code style is answered, formatting and
nullness checking included: the two projects are formatted with Fantomas
and build with nullness checking on, and only the verbatim port keeps
upstream's settings.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, in #51
with an instruction scoped to those files, Pascal-case assertion
functions and ImmutableArray for shared test data.

Two more answers of the same day: the Windows emulator lane of #45 runs
weekly instead of nightly (section 2), and the shared collection
functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the
global one (section 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the refactor/voption-collections branch from b62a75c to 68f317f Compare October 10, 2026 17:12
@xperiandri
xperiandri merged commit c634eac into main Oct 10, 2026
7 checks passed
@xperiandri
xperiandri deleted the refactor/voption-collections branch October 10, 2026 19:12
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The
open question of the adapter's code style is answered, formatting and
nullness checking included: the two projects are formatted with Fantomas
and build with nullness checking on, and only the verbatim port keeps
upstream's settings.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, in #51
with an instruction scoped to those files, Pascal-case assertion
functions and ImmutableArray for shared test data.

Two more answers of the same day: the Windows emulator lane of #45 runs
weekly instead of nightly (section 2), and the shared collection
functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the
global one (section 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The
open question of the adapter's code style is answered, formatting and
nullness checking included: the two projects are formatted with Fantomas
and build with nullness checking on, and only the verbatim port keeps
upstream's settings.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, in #51
with an instruction scoped to those files, Pascal-case assertion
functions and ImmutableArray for shared test data.

Two more answers of the same day: the Windows emulator lane of #45 runs
weekly instead of nightly (section 2), and the shared collection
functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the
global one (section 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The
open question of the adapter's code style is answered, formatting and
nullness checking included: the two projects are formatted with Fantomas
and build with nullness checking on, and only the verbatim port keeps
upstream's settings.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, in #51
with an instruction scoped to those files, Pascal-case assertion
functions and ImmutableArray for shared test data.

Two more answers of the same day: the Windows emulator lane of #45 runs
weekly instead of nightly (section 2), and the shared collection
functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the
global one (section 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants