Thanks for the tips, Michael.
I tried implementing a stand-alone Bind constructor for my Choice type.
I got it to compile, but it just evaluates the first Choice and drops everything else.
I think you're right about stack manipulation.
In the current implementation, each player already has a stack of choices.
Other Choice combinators like `And` push additional choices onto a player's stack, which then get processed in turn.
But each component Choice of `And` can be evaluated independently, whereas we have to pass information between the two component choices in a `Bind`.
Another detail that makes this challenging is that we actually have 2 Choice interpreters: one built into the game logic to decide how to modify game state, and another in the UI that decides how to present these choices, accept user input, and funnel that back into the game state. That's no problem for `And`, because the component Choices of `And` can be evaluated independently. We just push each of them on the stack and evaluate until the stack is empty.
So how do we pass information between the components of `Bind`?
Not sure, but I'm currently thinking I'll need something along these lines (code simplified to focus on the interesting bits).
I think it's in the spirit of what you suggested above.
What do you think?
data Choice
= ...
| DiscardAnyNumber
| Bind { ma :: Choice, lambda :: Choice }
| Lambda { a :: Maybe a, expression :: ChoiceExpression }
| EvalAndForwardResult { choice :: Choice } -- not sure if I should actually have a special choice for saving the result of a previous choice, or if I should just always save the result and discard it unless it's needed
-- I'll need a constructor for each kind of lambda I want to be able to express (for serialization)
data ChoiceExpression
= ...
| DrawN -- an interpreter will take the DrawN constructor and an Int and construct a Choice.DrawN
cellar = Bind
{ ma: EvalAndForwardResult { choice: DiscardAnyNumber } }
, lambda: Lambda { a: Nothing, expression: DrawN } -- the Nothing will be replaced with `Just a` after it's computed based on user input
}
-- game logic interpreter
resolveChoice choice = case choice of
...
Bind { ma, lambda } -> -- push ma and lambda onto the stack
EvalAndForwardResult { choice } -> -- eval choice, then expect a lambda to be on the stack and insert the result into it
Lambda { a, expression } -> -- by the time this is evaluated, a value should have been inserted for a, so we can construct a Choice from a and expression