-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/integrated shrinking #109
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I give a first look (mainly because I'm curious).
I find that reading the code with all these type annotations is quite complicated.
And, it is maybe just a stylistic concern but instead of directly defining the infix operator, I suggest to find it a name (... like the comment) and defining the infix operator in term of the named function.
I think that Selective API could be a great improvement as suggested by on the issue and you can add select a b = branch a b (pure (fun x -> x))
I couldn't disagree more :-). Personally I tend to annotate most functions; at least return types or arguments that are not the |
I'd love to hear more about this debate! I also tend to not write module Foo : sig …
end = struct …
end```).
Anyway for me it helps to annotate types so that future me can read the code back more easily. Types as documentation, in a way. |
When I work on datastructure, I usually have a |
Interesting. When I write against an interface I actually tend to not constrain it too early ( |
Only on save :) (but yes, I shutdown merlin regularly) |
I just pushed an MLI as well as put back I keep thinking |
@sir4ur0n so what do you think of putting all that in |
For ease of debugging generators/shrinkers, I have added a pretty printer to
This tells us it generated the value
|
thank you for all that work! I'll try to review it in a reasonable amount of time. Already wrote a patch for the printer 0001-refactor-pretty-printer.patch.gz which I find more readable :-) I noticed that some functions from |
I have enabled "Allow edits by maintainers" so feel free to push code changes directly on it, I will not feel offended 😄 About supporting old OCaml compiler versions:
|
@c-cube has a ... package for every occasion: https://github.com/c-cube/seq 🤓 |
Ahah I forgot. That's a transitional package dating back to when I added Seq to the stdlib :-).
I was indeed not thinking straight. We can depend on 4.03 on forward I imagine, unless I go and vendor the newer functions again.
|
I'm not sure I understand 😅 If we depend on 4.03 the problem of all the missing modules and functions (Seq, Int, Float, Option, etc.) is still there, isn't it? |
I meant 4.08, sorry :). Force of habit maybe?
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's excellent, thanks a lot for all the work. I think there are things to change, but I'm confident in the result.
- remove all the functional stuff for now, deal with it in Followup: Reimplement function shrinking in integrated shrinking #110
- a few smaller changes that are in comments
| _ -> false | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this might be very useful in practice:
| _ -> false | |
| _ -> false | |
let rec interleave s1 s2 () = | |
match s1() with | |
| Nil -> s2() | |
| Cons (x, s1') -> Cons (x, interleave s2 s1') |
let Tree (x0, xs) = a in | ||
let Tree (f0, fs) = f in | ||
let y = f0 x0 in | ||
let ys = Seq.append (Seq.map (fun f' -> ap f' a) fs) (Seq.map (fun x' -> ap f x') xs) in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let ys = Seq.append (Seq.map (fun f' -> ap f' a) fs) (Seq.map (fun x' -> ap f x') xs) in | |
let ys = Seq.interleave (Seq.map (fun f' -> ap f' a) fs) (Seq.map (fun x' -> ap f x') xs) in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this implementation break some applicative laws that we may want to respect?
It breaks at least the composition property:
pure (fun g f x -> g (f x)) <*> u <*> v <*> w = u <*> (v <*> w)
but I don't know if this can lead to problems along the way, maybe we don't care
Also: functionally speaking, what is the benefit of such interleaving?
I think a benefit is that if the problem is on value a
(no matter the function a -> b
), it will try faster shrinks of a
(instead of first trying to exhaust all shrinks of f
, and also trying again all shrinks of f
at each shrink of a
before further shrinking a
), am I correct?
This is a sensible benefit indeed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The (expected) benefit is indeed to be able to try shrinks of b
before we exhaust the (direct) shrinks of a
, so it's more "fair".
I don't think it breaks the composition property, since we don't really have a notion of equality on trees 😏 . If you consider shrinks as a set, the associativity still holds; is order of shrinks something we should care about algebraically?
end | ||
|
||
(** An observable is a random function {i argument}. *) | ||
module Observable : sig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's remove that :)
type _ fun_repr | ||
(** Internal data for functions. A ['f fun_] is a function | ||
of type ['f], fundamentally. *) | ||
|
||
(** A function packed with the data required to print/shrink it. See {!Fn} | ||
to see how to apply, print, etc. such a function. | ||
|
||
One can also directly pattern match on it to obtain | ||
the executable function. | ||
|
||
For example: | ||
{[ | ||
QCheck.Test.make | ||
QCheck.(pair (fun1 Observable.int bool) (small_list int)) | ||
(fun (Fun (_,f), l) -> l=(List.rev_map f l |> List.rev l)) | ||
]} | ||
*) | ||
type _ fun_ = | ||
| Fun : 'f fun_repr * 'f -> 'f fun_ | ||
|
||
(** Utils on functions | ||
@since 0.6 *) | ||
module Fn : sig | ||
type 'a t = 'a fun_ | ||
|
||
val print : _ t Print.t | ||
|
||
val apply : 'f t -> 'f | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all that should go
(shrinking will be faster). | ||
@since 0.6 *) | ||
|
||
module Tuple : sig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
val fun_nary : 'a Tuple.obs -> 'b arbitrary -> ('a Tuple.t -> 'b) fun_ arbitrary | ||
(** [fun_nary] makes random n-ary functions. | ||
Example: | ||
{[ | ||
let module O = Observable in | ||
fun_nary Tuple.(O.int @-> O.float @-> O.string @-> o_nil) bool) | ||
]} | ||
@since 0.6 *) | ||
|
||
val fun2 : | ||
'a Observable.t -> | ||
'b Observable.t -> | ||
'c arbitrary -> | ||
('a -> 'b -> 'c) fun_ arbitrary | ||
(** @since 0.6 *) | ||
|
||
val fun3 : | ||
'a Observable.t -> | ||
'b Observable.t -> | ||
'c Observable.t -> | ||
'd arbitrary -> | ||
('a -> 'b -> 'c -> 'd) fun_ arbitrary | ||
(** @since 0.6 *) | ||
|
||
val fun4 : | ||
'a Observable.t -> | ||
'b Observable.t -> | ||
'c Observable.t -> | ||
'd Observable.t -> | ||
'e arbitrary -> | ||
('a -> 'b -> 'c -> 'd -> 'e) fun_ arbitrary | ||
(** @since 0.6 *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
Bumped the min OCaml version to 4.08 in 1ceea87 |
Co-authored-by: Simon Cruanes <[email protected]>
21c9df0
to
40014f3
Compare
@sir4ur0n sorry for the loss of synchronization; is this ready for review again? |
No, I lack time to work on it these days 😞 I have fixed some issues but definitely not all of them yet. I'll ping you when it's done Edit: I just saw you merged it, is that intentional? 😅 |
Yes I did, I thought it was ready and I was stalling it … :p |
I just force pushed back, sorry. We need to fix #115 first :) |
Closes #106
This is a WIP MR, many things are still pending, I will try to list them below
list_towards
stuff)Gen.t
is lazy enoughint_bound
shuffle_a
to make a copy instead of mutating?add_shrink_invariant
? If yes: maybe this should be done in another MR to add cleaner post-filteringmap_keep_input
? IMHO it becomes useless (commented out)mli