-
Notifications
You must be signed in to change notification settings - Fork 107
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
Use state to update a Command's input #456
base: master
Are you sure you want to change the base?
Use state to update a Command's input #456
Conversation
4139585
to
c228279
Compare
I'm focusing on the other PR at the moment but I did have a thought about this one. Maybe we can do something with |
Looking at the docs... I might be missing something, but I don't think so, unfortunately. What I think we want is for a way to use the pattern Command {commandGen, commandExecute, commandCallbacks} <-
CommandA commandGen _ commandExecute commandCallbacks | False might be valid? I'm not sure. The existential type variables might cause problems. The other is that record patterns aren't allowed to be explicitly bidirectional. ("Because the rhs expr might be constructing different data constructors", though I don't really know what the docs mean by that.) So even if we could get past the first problem, we wouldn't be able to use record syntax. It would basically just be a smart constructor that starts with a capital letter. |
Though, another possibility: add a Presumably the class method would either replace the existing class ToAction cmd where
action ::
(MonadGen gen, MonadTest m)
=> [cmd gen m state]
-> GenT (StateT (Context state) (GenBase gen)) (Action m state) or the class method would only take a single This wouldn't be fully backwards compatible, in that It would make users choose "either all commands are simple or all are complex", but |
This allows you to write commands whose input changes when a previous Command shrinks. For example, suppose the state contains `someList :: [Bool]` and you have a command whose input expects "index into `someList` pointing at `True`". You can generate that index directly, but if an earlier command shrinks, `someList` might change and your index now points at `False`. (This is contrived, but hopefully points at the sorts of more complicated situations where it might be useful.) With this you can instead generate a number between 0 and (the number of `True` elements). Then use `mkInput` to turn that number into an index into `someList`. This will still be valid as long as the number of `True` elements doesn't shrink below the generated value. You could also pass this number directly into `exec`. But then in `exec` you'd need to get `someList` directly from the concrete model, which might be complicated and/or slow. I implemented this by adding a new `Command` constructor, `CommandA` where A is for Advanced. I don't love this. I could have simply changed the existing constructor, but that means every existing Command needs to be updated. (It's a simple change, `commandMkInput = const Just` means they work as before, but still a massive pain.) The downside of this approach is implementation complexity, plus any user functions taking a `Command` as input may need to be updated. Other approaches we could take here: 1. We could pass the concrete state into `exec` along with the concrete input. Then you wouldn't need to get `someList` from the model. But you might still need to do complicated calculations in `exec` which could make the failure output hard to follow. If we did this we'd have the same tradeoff between changing the existing constructor and adding a new one. 2. We could add a callback ```haskell MapMaybeInput (state Symbolic -> input Symbolic -> Maybe (input Symbolic) ``` Each of these would be applied in turn to update the input. (`Require` would be equivalent to a `MapMaybeInput` that ignores state, and either returns the input unchanged or `Nothing`.) This would be compatible with existing commands, but functions accepting a `Command` or a `Callback` as input might still need changing.
We don't need `actionInput0`, because it was only used to pass to `actionRefreshInput`. So we can just hide it in a closure.
9bee2a8
to
485cbb5
Compare
@jacobstanley Gentle ping - it would be great if you could take a look at this when you get a chance. |
This allows you to write commands whose input changes when a previous
Command shrinks.
For example, suppose the state contains
someList :: [Bool]
andyou have a command whose input expects "index into
someList
pointingat
True
". You can generate that index directly, but if an earliercommand shrinks,
someList
might change and your index now points atFalse
.(This is contrived, but hopefully points at the sorts of more
complicated situations where it might be useful.)
With this you can instead generate a number between 0 and (the number of
True
elements). Then usemkInput
to turn that number into an indexinto
someList
. This will still be valid as long as the number ofTrue
elements doesn't shrink below the generated value.You could also pass this number directly into
exec
. But then inexec
you'd need to get
someList
directly from the concrete model, whichmight be complicated and/or slow.
I implemented this by adding a new
Command
constructor,CommandA
where A is for Advanced. I don't love this. I could have simply changed
the existing constructor, but that means every existing Command needs to
be updated. (It's a simple change,
commandMkInput = const Just
meansthey work as before, but still a massive pain.) The downside of this
approach is implementation complexity, plus any user functions taking a
Command
as input may need to be updated.Other approaches we could take here:
We could pass the concrete state into
exec
along with the concreteinput. Then you wouldn't need to get
someList
from the model. But youmight still need to do complicated calculations in
exec
which couldmake the failure output hard to follow.
If we did this we'd have the same tradeoff between changing the
existing constructor and adding a new one.
We could add a callback
Each of these would be applied in turn to update the input.
(
Require
would be equivalent to aMapMaybeInput
that ignoresstate, and either returns the input unchanged or
Nothing
.)This would be compatible with existing commands, but functions
accepting a
Command
or aCallback
as input might still needchanging.
This PR includes #453, and was merged into my own fork as
ChickenProp#3.