From 50662cef030307da79350d335158dd197bba2d74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:06:25 +0000 Subject: [PATCH 1/4] Initial plan From 12e18066175d9d77ab991df3c12fc9f41f5552e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:13:20 +0000 Subject: [PATCH 2/4] Add Gen.pick and Gen.tryPick functions with C# API support Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com> --- src/FsCheck/FSharp.Gen.fs | 28 ++++++++++++++++++++++++++++ src/FsCheck/Fluent.Gen.fs | 23 +++++++++++++++++++++++ tests/FsCheck.Test/Gen.fs | 14 ++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/src/FsCheck/FSharp.Gen.fs b/src/FsCheck/FSharp.Gen.fs index 3df825c4..d5665091 100644 --- a/src/FsCheck/FSharp.Gen.fs +++ b/src/FsCheck/FSharp.Gen.fs @@ -400,6 +400,34 @@ module Gen = [] let filter (predicate:'T->bool) generator = where predicate generator + ///Tries to apply the given chooser function to successive values generated by the given generator, + ///returning the first result where the function returns Some(x). This function 'gives up' by generating None + ///if the given original generator did not generate any values for which the chooser function returned Some(x), + ///after trying to get values by increasing its size. + //[category: Creating generators from generators] + [] + let tryPick (chooser:'T->option<'U>) generator = + let rec tryValue k s = + match (k,s) with + | (_,0) -> constant None + | (k,s) -> (resize (2*k+s) generator) |> bind (fun x -> + match chooser x with + | Some v -> constant (Some v) + | None -> tryValue (k+1) (s-1)) + sized (max 1 >> tryValue 0) + + ///Applies the given chooser function to successive values generated by the given generator, + ///returning the first result where the function returns Some(x). Contrary to tryPick, this function + ///keeps re-trying by increasing the size of the original generator ad infinitum. Make sure there is + ///a high probability that the chooser function returns Some for some values. + //[category: Creating generators from generators] + [] + let rec pick (chooser:'T->option<'U>) generator = + tryPick chooser generator |> bind (fun mx -> + match mx with + | Some x -> constant x + | None -> sized (fun n -> resize (n+1) (pick chooser generator))) + let inline private shuffleInPlace (arr: array<_>) = // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle let inline swap (arr : array<_>) i j = diff --git a/src/FsCheck/Fluent.Gen.fs b/src/FsCheck/Fluent.Gen.fs index 409f652d..cca66123 100644 --- a/src/FsCheck/Fluent.Gen.fs +++ b/src/FsCheck/Fluent.Gen.fs @@ -266,6 +266,29 @@ type Gen = if isNull predicate then nullArg "predicate" source |> Gen.where predicate.Invoke + /// Tries to apply the given chooser function to successive values generated by the source generator, + /// returning the first result where the function returns a value. This function 'gives up' by generating null + /// if the source generator did not generate any values for which the chooser function returned a value, + /// after trying to get values by increasing its size. + [] + static member TryPick(source:Gen<'T>, chooser : Func<'T, 'U>) = + if isNull chooser then nullArg "chooser" + source |> Gen.tryPick (fun x -> + let result = chooser.Invoke(x) + if isNull (box result) then None else Some result) + |> Gen.map (function Some v -> v | None -> Unchecked.defaultof<'U>) + + /// Applies the given chooser function to successive values generated by the source generator, + /// returning the first result where the function returns a non-null value. Contrary to TryPick, + /// this function keeps re-trying by increasing the size of the original generator ad infinitum. + /// Make sure there is a high probability that the chooser function returns a non-null value for some values. + [] + static member Pick(source:Gen<'T>, chooser : Func<'T, 'U>) = + if isNull chooser then nullArg "chooser" + source |> Gen.pick (fun x -> + let result = chooser.Invoke(x) + if isNull (box result) then None else Some result) + ///Generates a list of given length, containing values generated by the given generator. //[category: Creating generators from generators] [] diff --git a/tests/FsCheck.Test/Gen.fs b/tests/FsCheck.Test/Gen.fs index 36670fcd..c3833c57 100644 --- a/tests/FsCheck.Test/Gen.fs +++ b/tests/FsCheck.Test/Gen.fs @@ -241,6 +241,20 @@ module Gen = |> sample1 |> ((=) (abs v)) ) + [] + let TryPick (v:int) (chooser:int -> option) = + let expected = chooser v + assertTrue ( Gen.tryPick chooser (Gen.constant v) + |> sample1 + |> ((=) expected) ) + + [] + let Pick (v:int) = + let chooser x = if x >= 0 then Some (string x) else None + assertTrue ( Gen.pick chooser (Gen.elements [v;abs v]) + |> sample1 + |> ((=) (string (abs v))) ) + [] let ListOf (NonNegativeInt size) (v:char) = assertTrue ( Gen.resize size (Gen.listOf <| Gen.constant v) From a80367ba71baa5cd3f0eed7a7f85b8c3848bb9d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:15:37 +0000 Subject: [PATCH 3/4] Add explanatory comments for resize formula and boxing in C# API Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com> --- src/FsCheck/FSharp.Gen.fs | 1 + src/FsCheck/Fluent.Gen.fs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/FsCheck/FSharp.Gen.fs b/src/FsCheck/FSharp.Gen.fs index d5665091..d8419ed9 100644 --- a/src/FsCheck/FSharp.Gen.fs +++ b/src/FsCheck/FSharp.Gen.fs @@ -410,6 +410,7 @@ module Gen = let rec tryValue k s = match (k,s) with | (_,0) -> constant None + // Resize with 2*k+s to progressively increase size with each retry attempt | (k,s) -> (resize (2*k+s) generator) |> bind (fun x -> match chooser x with | Some v -> constant (Some v) diff --git a/src/FsCheck/Fluent.Gen.fs b/src/FsCheck/Fluent.Gen.fs index cca66123..80665e4e 100644 --- a/src/FsCheck/Fluent.Gen.fs +++ b/src/FsCheck/Fluent.Gen.fs @@ -275,6 +275,7 @@ type Gen = if isNull chooser then nullArg "chooser" source |> Gen.tryPick (fun x -> let result = chooser.Invoke(x) + // Boxing allows null checking for both reference and value types if isNull (box result) then None else Some result) |> Gen.map (function Some v -> v | None -> Unchecked.defaultof<'U>) @@ -287,6 +288,7 @@ type Gen = if isNull chooser then nullArg "chooser" source |> Gen.pick (fun x -> let result = chooser.Invoke(x) + // Boxing allows null checking for both reference and value types if isNull (box result) then None else Some result) ///Generates a list of given length, containing values generated by the given generator. From a4028100962cce8b59eb321d0421f8c467be5efe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 20:34:00 +0000 Subject: [PATCH 4/4] Remove C# API wrappers for pick and tryPick Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com> --- src/FsCheck/Fluent.Gen.fs | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/FsCheck/Fluent.Gen.fs b/src/FsCheck/Fluent.Gen.fs index 80665e4e..409f652d 100644 --- a/src/FsCheck/Fluent.Gen.fs +++ b/src/FsCheck/Fluent.Gen.fs @@ -266,31 +266,6 @@ type Gen = if isNull predicate then nullArg "predicate" source |> Gen.where predicate.Invoke - /// Tries to apply the given chooser function to successive values generated by the source generator, - /// returning the first result where the function returns a value. This function 'gives up' by generating null - /// if the source generator did not generate any values for which the chooser function returned a value, - /// after trying to get values by increasing its size. - [] - static member TryPick(source:Gen<'T>, chooser : Func<'T, 'U>) = - if isNull chooser then nullArg "chooser" - source |> Gen.tryPick (fun x -> - let result = chooser.Invoke(x) - // Boxing allows null checking for both reference and value types - if isNull (box result) then None else Some result) - |> Gen.map (function Some v -> v | None -> Unchecked.defaultof<'U>) - - /// Applies the given chooser function to successive values generated by the source generator, - /// returning the first result where the function returns a non-null value. Contrary to TryPick, - /// this function keeps re-trying by increasing the size of the original generator ad infinitum. - /// Make sure there is a high probability that the chooser function returns a non-null value for some values. - [] - static member Pick(source:Gen<'T>, chooser : Func<'T, 'U>) = - if isNull chooser then nullArg "chooser" - source |> Gen.pick (fun x -> - let result = chooser.Invoke(x) - // Boxing allows null checking for both reference and value types - if isNull (box result) then None else Some result) - ///Generates a list of given length, containing values generated by the given generator. //[category: Creating generators from generators] []