Skip to content

Commit 242bc4f

Browse files
authored
Merge pull request #400 from TysonMN/applicative_CE
Applicative CE
2 parents 15f2524 + fda88a3 commit 242bc4f

File tree

5 files changed

+89
-45
lines changed

5 files changed

+89
-45
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- Add `Tree.apply`. Change `Gen.apply` from monadic to applicative. Revert runtime optimization of `Gen.integral`. ([#398][398], [@TysonMN][TysonMN])
44
- Change `ListGen.traverse` from monadic to applicative. ([#399][399], [@TysonMN][TysonMN])
55
- Fix bug in the `BindReturn` method of the `property` CE where the generated value is not added to the Journal. ([#401][401], [@TysonMN][TysonMN])
6+
- Add `BindReturn` to the `gen` CE. This essentially changes the last call to `let!` to use `Gen.map` instead of `Gen.bind`. Add `MergeSources` to the `gen` and `property` CEs. This change enables the `and!` syntax. ([#400][400], [@TysonMN][TysonMN])
67

78
## Version 0.12.0 (2021-12-12)
89

@@ -193,6 +194,8 @@
193194

194195
[401]:
195196
https://github.com/hedgehogqa/fsharp-hedgehog/pull/401
197+
[400]:
198+
https://github.com/hedgehogqa/fsharp-hedgehog/pull/400
196199
[399]:
197200
https://github.com/hedgehogqa/fsharp-hedgehog/pull/399
198201
[398]:

src/Hedgehog/Gen.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ module Gen =
103103
constant ()
104104
member __.Return(a) : Gen<'a> = constant a
105105
member __.ReturnFrom(g) : Gen<'a> = g
106+
member __.BindReturn(g, f) = map f g
107+
member __.MergeSources(ga, gb) = zip ga gb
106108
member __.Bind(g, f) = g |> bind f
107109
member __.For(xs, k) =
108110
let xse = (xs :> seq<'a>).GetEnumerator ()

src/Hedgehog/Property.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ module PropertyBuilder =
330330
|> Property.ofGen
331331
|> Property.map f
332332

333+
member __.MergeSources(ga, gb) =
334+
Gen.zip ga gb
335+
333336
member __.ReturnFrom(m : Property<'a>) : Property<'a> =
334337
m
335338

tests/Hedgehog.Tests/GenTests.fs

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,50 @@ open Hedgehog
55
open Hedgehog.Gen.Operators
66
open TestDsl
77

8+
9+
let private testGenPairViaApply gPair =
10+
// In addition to asserting that Gen.apply is applicative, this code
11+
// also asserts that the integral shrink tree is the one containing
12+
// duplicates that existed before PR
13+
// https://github.com/hedgehogqa/fsharp-hedgehog/pull/239
14+
// The duplicate-free shrink trees that result from the code in that PR
15+
// do not work well with the applicative behavior of Gen.apply because
16+
// some values would shrink more if using the monadic version of
17+
// Gen.apply, which should never happen.
18+
let actual =
19+
seq {
20+
while true do
21+
let t = gPair |> Gen.sampleTree 0 1 |> Seq.head
22+
if Tree.outcome t = (2, 1) then
23+
yield t
24+
} |> Seq.head
25+
26+
let expected =
27+
Node ((2, 1), [
28+
Node ((0, 1), [
29+
Node ((0, 0), [])
30+
])
31+
Node ((1, 1), [
32+
Node ((0, 1), [
33+
Node ((0, 0), [])
34+
])
35+
Node ((1, 0), [
36+
Node ((0, 0), [])
37+
])
38+
])
39+
Node ((2, 0), [
40+
Node ((0, 0), [])
41+
Node ((1, 0), [
42+
Node ((0, 0), [])
43+
])
44+
])
45+
])
46+
47+
(actual |> Tree.map (sprintf "%A") |> Tree.render)
48+
=! (expected |> Tree.map (sprintf "%A") |> Tree.render)
49+
Expect.isTrue <| Tree.equals actual expected
50+
51+
852
let genTests = testList "Gen tests" [
953
yield! testCases "dateTime creates DateTime instances"
1054
[ 8; 16; 32; 64; 128; 256; 512 ] <| fun count->
@@ -37,21 +81,23 @@ let genTests = testList "Gen tests" [
3781
[] =! List.filter (fun ch -> ch = char nonchar) actual
3882

3983
testCase "dateTime randomly generates value between max and min ticks" <| fun _ ->
40-
let seed0 = Seed.random ()
41-
let (seed1, _) = Seed.split seed0
84+
// This is a bad test because essentially the same logic used to
85+
// implement Gen.dateTime appears in this test. However, keeping it for
86+
// now.
87+
let seed = Seed.random ()
4288
let range =
4389
Range.constant
4490
DateTime.MinValue.Ticks
4591
DateTime.MaxValue.Ticks
4692
let ticks =
4793
Random.integral range
48-
|> Random.run seed1 0
94+
|> Random.run seed 0
4995

5096
let actual =
5197
Range.constant DateTime.MinValue DateTime.MaxValue
5298
|> Gen.dateTime
5399
|> Gen.toRandom
54-
|> Random.run seed0 0
100+
|> Random.run seed 0
55101
|> Tree.outcome
56102

57103
let expected = DateTime ticks
@@ -135,51 +181,20 @@ let genTests = testList "Gen tests" [
135181
}
136182
|> Property.check
137183

138-
testCase "apply is applicative" <| fun () ->
139-
// In addition to asserting that Gen.apply is applicative, this test
140-
// also asserts that the integral shrink tree is the one containing
141-
// duplicates that existed before PR
142-
// https://github.com/hedgehogqa/fsharp-hedgehog/pull/239
143-
// The duplicate-free shrink trees that result from the code in that PR
144-
// do not work well with the applicative behavior of Gen.apply because
145-
// some values would shrink more if using the monadic version of
146-
// Gen.apply, which should never happen.
184+
testCase "apply is applicative via function" <| fun () ->
147185
let gPair =
148186
Gen.constant (fun a b -> a, b)
149187
|> Gen.apply (Range.constant 0 2 |> Gen.int32)
150188
|> Gen.apply (Range.constant 0 1 |> Gen.int32)
189+
testGenPairViaApply gPair
151190

152-
let actual =
153-
seq {
154-
while true do
155-
let t = gPair |> Gen.sampleTree 0 1 |> Seq.head
156-
if Tree.outcome t = (2, 1) then
157-
yield t
158-
} |> Seq.head
159-
160-
let expected =
161-
Node ((2, 1), [
162-
Node ((0, 1), [
163-
Node ((0, 0), [])
164-
])
165-
Node ((1, 1), [
166-
Node ((0, 1), [
167-
Node ((0, 0), [])
168-
])
169-
Node ((1, 0), [
170-
Node ((0, 0), [])
171-
])
172-
])
173-
Node ((2, 0), [
174-
Node ((0, 0), [])
175-
Node ((1, 0), [
176-
Node ((0, 0), [])
177-
])
178-
])
179-
])
180-
181-
(actual |> Tree.map (sprintf "%A") |> Tree.render)
182-
=! (expected |> Tree.map (sprintf "%A") |> Tree.render)
183-
Expect.isTrue <| Tree.equals actual expected
191+
testCase "apply is applicative via CE" <| fun () ->
192+
let gPair =
193+
gen {
194+
let! a = Range.constant 0 2 |> Gen.int32
195+
and! b = Range.constant 0 1 |> Gen.int32
196+
return a, b
197+
}
198+
testGenPairViaApply gPair
184199

185200
]

tests/Hedgehog.Tests/PropertyTests.fs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,25 @@ let propertyTests = testList "Property tests" [
110110

111111
actual =! "false"
112112

113+
testCase "and! syntax is applicative" <| fun () ->
114+
// Based on https://well-typed.com/blog/2019/05/integrated-shrinking/#:~:text=For%20example%2C%20consider%20the%20property%20that
115+
let actual =
116+
property {
117+
let! x = Range.constant 0 1_000_000_000 |> Gen.int32
118+
and! y = Range.constant 0 1_000_000_000 |> Gen.int32
119+
return x <= y |> Expect.isTrue
120+
}
121+
|> Property.report
122+
|> Report.render
123+
|> (fun x -> x.Split ([|Environment.NewLine|], StringSplitOptions.None))
124+
|> Array.item 1
125+
126+
let actual =
127+
// normalize printing of a pair between .NET and Fable/JS
128+
actual.Replace("(", "")
129+
.Replace(" ", "")
130+
.Replace(")", "")
131+
132+
actual =! "1,0"
133+
113134
]

0 commit comments

Comments
 (0)