Skip to content

Commit b7b0a24

Browse files
authored
Merge pull request #465 from hedgehogqa/add-missing-linq-combinators
Add missing LINQ combinators
2 parents 01e6726 + 707f232 commit b7b0a24

File tree

6 files changed

+85
-0
lines changed

6 files changed

+85
-0
lines changed

src/Hedgehog/Gen.Traversable.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ module GenTraversable =
1818
|> Gen.apply (f a))
1919
<| ma
2020

21+
/// Apply a generator-producing function to each element and collect the results.
2122
let traverse (f: 'a -> Gen<'b>) (ma: #seq<'a>) : Gen<seq<'b>> =
2223
traverse' f ma |> Gen.map Seq.ofList
2324

25+
/// Turn a sequence of generators into a generator of a sequence.
2426
let sequence (gens : #seq<Gen<'a>>) : Gen<seq<'a>> =
2527
gens |> traverse id
2628

29+
/// Apply a generator-producing function to each list element and collect the results.
2730
let traverseList (f: 'a -> Gen<'b>) (ma: List<'a>) : Gen<List<'b>> =
2831
traverse' f ma
2932

33+
/// Turn a list of generators into a generator of a list.
3034
let sequenceList (gens : List<Gen<'a>>) : Gen<List<'a>> =
3135
gens |> traverseList id

src/Hedgehog/Gen.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ module Gen =
2323
let delay (f : unit -> Gen<'a>) : Gen<'a> =
2424
Random.delay (toRandom << f) |> ofRandom
2525

26+
/// Ensures a cleanup function runs after a generator executes, even if it throws an exception.
2627
let tryFinally (after : unit -> unit) (m : Gen<'a>) : Gen<'a> =
2728
toRandom m |> Random.tryFinally after |> ofRandom
2829

30+
/// Catches exceptions thrown by a generator and handles them with a recovery function.
2931
let tryWith (k : exn -> Gen<'a>) (m : Gen<'a>) : Gen<'a> =
3032
toRandom m |> Random.tryWith (toRandom << k) |> ofRandom
3133

src/Hedgehog/Hedgehog.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ Failures are automatically simplified, giving developers coherent, intelligible
7171
<Compile Include="Linq\Gen.fs" />
7272
<Compile Include="Linq\Gen.Extensions.fs" />
7373
<Compile Include="Linq\Gen.Collections.fs" />
74+
<Compile Include="Linq\Gen.Functions.fs" />
7475
<Compile Include="Linq\AutoGenExtensions.fs" />
7576
<Compile Include="Linq\PropertyConfig.fs" />
7677
<Compile Include="Linq\Property.fs" />

src/Hedgehog/Linq/Gen.Collections.fs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Hedgehog.Linq
22

3+
open System
34
open System.Runtime.CompilerServices
45
open Hedgehog
56
open Hedgehog.FSharp
@@ -23,3 +24,13 @@ type GenListExtensions() =
2324
[<Extension>]
2425
static member AddElement(self : Gen<ResizeArray<'T>>, x : 'T) =
2526
self |> Gen.map List.ofSeq |> Gen.addElement x |> Gen.map ResizeArray
27+
28+
/// Turn a sequence of generators into a generator of a sequence.
29+
[<Extension>]
30+
static member Sequence(self : #seq<Gen<'T>>) : Gen<seq<'T>> =
31+
self |> Gen.sequence
32+
33+
/// Apply a generator-producing function to each element and collect the results.
34+
[<Extension>]
35+
static member Traverse(self : #seq<'T>, f : Func<'T, Gen<'TResult>>) : Gen<seq<'TResult>> =
36+
self |> Gen.traverse f.Invoke

src/Hedgehog/Linq/Gen.Extensions.fs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,26 @@ type GenExtensions private () =
209209
static member ToRandom (gen : Gen<'T>) : Random<Tree<'T>> =
210210
Gen.toRandom gen
211211

212+
/// <summary>
213+
/// Ensures a cleanup action runs after the generator executes, even if an exception is thrown.
214+
/// </summary>
215+
/// <param name="gen">The generator to wrap with cleanup logic.</param>
216+
/// <param name="after">Action to execute after the generator completes or fails.</param>
212217
[<Extension>]
213218
static member TryFinally (gen : Gen<'T>, after : Action) : Gen<'T> =
214219
Gen.tryFinally after.Invoke gen
215220

221+
/// <summary>
222+
/// Catches exceptions thrown by a generator and handles them with a recovery function.
223+
/// Use this to provide fallback behavior when a generator might throw an exception.
224+
/// </summary>
225+
/// <param name="gen">The generator that might throw an exception.</param>
226+
/// <param name="after">Function that receives the exception and returns a recovery generator.</param>
227+
/// <example>
228+
/// <code>
229+
/// var gen = riskyGen.TryWith(ex => Gen.Constant(defaultValue));
230+
/// </code>
231+
/// </example>
216232
[<Extension>]
217233
static member TryWith (gen : Gen<'T>, after : Func<exn, Gen<'T>>) : Gen<'T> =
218234
Gen.tryWith after.Invoke gen

src/Hedgehog/Linq/Gen.Functions.fs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
namespace Hedgehog.Linq
2+
3+
open System
4+
open System.Runtime.CompilerServices
5+
open Hedgehog
6+
open Hedgehog.FSharp
7+
8+
[<AbstractClass; Sealed>]
9+
type GenFunctionExtensions private () =
10+
11+
/// <summary>
12+
/// Generates a list together with a function that maps each of the distinct
13+
/// elements in the list to values generated by outGen.
14+
/// </summary>
15+
/// <remarks>
16+
/// Distinct elements in the input list may map to the same output values.
17+
/// For example, [2; 3; 2] may map to ['A'; 'B'; 'A'] or ['A'; 'A'; 'A'],
18+
/// but never ['A'; 'B'; 'C']. The generated function throws if called with
19+
/// values not present in the input list.
20+
/// </remarks>
21+
/// <param name="inpGen">Generator for the input list.</param>
22+
/// <param name="outGen">Generator for the output values.</param>
23+
/// <returns>A generator that produces a tuple of the input list and a mapping function.</returns>
24+
[<Extension>]
25+
static member WithMapTo(inpGen : Gen<ResizeArray<'T>>, outGen : Gen<'TResult>) : Gen<struct (ResizeArray<'T> * Func<'T, 'TResult>)> =
26+
inpGen
27+
|> Gen.map List.ofSeq
28+
|> Gen.withMapTo outGen
29+
|> Gen.map (fun (inputs, f) -> struct (ResizeArray inputs, Func<'T, 'TResult>(f)))
30+
31+
/// <summary>
32+
/// Generates a list together with a function that maps each of the distinct
33+
/// elements in the list to distinct values generated by outGen.
34+
/// </summary>
35+
/// <remarks>
36+
/// Distinct elements in the input list are guaranteed to map to distinct
37+
/// output values. For example, [2; 3; 2] may map to ['A'; 'B'; 'A'], but
38+
/// never ['A'; 'A'; 'A'] or ['A'; 'B'; 'C']. Only use this if the output
39+
/// space is large enough that the required number of distinct output values
40+
/// are likely to be generated. The generated function throws if called with
41+
/// values not present in the input list.
42+
/// </remarks>
43+
/// <param name="inpGen">Generator for the input list.</param>
44+
/// <param name="outGen">Generator for the output values.</param>
45+
/// <returns>A generator that produces a tuple of the input list and a mapping function.</returns>
46+
[<Extension>]
47+
static member WithDistinctMapTo(inpGen : Gen<ResizeArray<'T>>, outGen : Gen<'TResult>) : Gen<struct(ResizeArray<'T> * Func<'T, 'TResult>)> =
48+
inpGen
49+
|> Gen.map List.ofSeq
50+
|> Gen.withDistinctMapTo outGen
51+
|> Gen.map (fun (inputs, f) -> struct (ResizeArray inputs, Func<'T, 'TResult>(f)))

0 commit comments

Comments
 (0)