Skip to content
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

Extra args to MultisetEvaluator? #218

Open
HighDiceRoller opened this issue Feb 4, 2025 · 5 comments
Open

Extra args to MultisetEvaluator? #218

HighDiceRoller opened this issue Feb 4, 2025 · 5 comments

Comments

@HighDiceRoller
Copy link
Owner

These would be constant over the lifetime of an evaluation and sent to next_state etc. Possibly keyword arguments only in order to avoid forcing everyone to add additional arguments to next_state etc.

@HighDiceRoller
Copy link
Owner Author

HighDiceRoller commented Feb 8, 2025

This also brings up the question of early/late binding in nested closures. Compare a recent example:

@multiset_function
def total_and_burst(rolls):
    return rolls.sum(), rolls.keep_outcomes([dtn]).count()

with

@multiset_function
def total_and_burst(rolls):
    return rolls.sum(), rolls.keep_outcomes(lambda x: x == dtn).count()

In the first case we have early binding of dtn since the list is evaluated during @multiset_function, whereas in the second we have late binding of dtn due to the lambda. Should @multiset_function be changed to late binding for consistency? Though the evaluator also assumes no side effects so cases where this would matter are fraught to begin with.

Here it may be preferable if @multiset_function supported extra args:

@multiset_function
def total_and_burst(rolls, *, dtn):
    return rolls.sum(), rolls.keep_outcomes([dtn]).count()

Now there is no worry about side effects. But how can we delay evaluating dtn in the body? I think we would have to delay creation of the actual evaluator until the evaluation is actually requested. At that point, we would create an inner evaluator (with a cache based on dtn), or possibly multiple evaluators if dtn is a die or other expandable, and then run the evaluation. I think this is independent of MultisetEvaluator itself taking **kwargs? Except that both MultisetFunctionEvaluator creation and actual evaluation both pass through evaluate(), so the delayed final evaluator for @multiset_function might need to be created first.

JointEvaluator is troublesome since it depends on next_state() which means that we can't override evaluate() within it. Would the solution to be to put the joint under the delay as well? Would even this work with nesting?

@HighDiceRoller
Copy link
Owner Author

HighDiceRoller commented Feb 9, 2025

What about the interaction with NoExpand? Given that I have no plans to allow new rolls to be introduced inside a multiset evaluation, there's no reason for these extra arguments (if implemented) not to expand. Should we ditch NoExpand and use kwargs for non-expanding arguments instead? This does have the benefit that expansion is shown in the function rather than just the call site. On the other hand, this would create a mismatch between the @multiset_function case where kwargs are expanded and map() where they are not.

@HighDiceRoller
Copy link
Owner Author

HighDiceRoller commented Feb 16, 2025

Could this be solved by splitting the evaluation into an expression builder stage and an evaluation stage? When we do __call__ or evaluate, either we are in a @multiset_function, or we are actually running the evaluation.

If we are actually running the evaluation, we could proceed similar to now, though if we want to expand the arguments a la map() then we need to spawn multiple evaluations.

If it is inside a @multiset_function, we can't instantiate the actual evaluation until we get the actual arguments. So maybe we delay creating the MultisetFunctionEvaluator until then?

There would also seem to be a different meaning of keyword arguments between the two cases. In a normal evaluation they are proposed to be sent to next_state etc., whereas if they are arguments to a function decorated with @multiset_function then they are presumably possibly used in other ways than next_state as in the previous example (but could also be sent to next_state if instructed to do so?) Is this a problem?

Should JointEvaluator only wrap MultisetFunctionEvaluators? Would it even be a separate class?

So what then is an evaluation? Effectively a tuple of next_state, final_outcome, order, kwargs with an associated cache? For basic evaluators next_state is always the same, but for MultisetFunctionEvaluator it may not be. The outermost evaluator has to hold the cache since only it knows about all the correlations.

@HighDiceRoller
Copy link
Owner Author

We'll also eventually want to do unbinding after the MixtureExpression is prepared -- this way we're only considering one actual expression at a time.

@HighDiceRoller
Copy link
Owner Author

HighDiceRoller commented Feb 26, 2025

Naming:

For the user-visible object:

  • MultisetEvaluator - probably given to a subclass which provides default preparation
  • MultisetEvaluatorBase

For the preparation method:

  • prepare, ready
  • run, go - possibly too late of an implication, we still have to run the actual evaluation
  • track, trace, branch, graph
  • closure, environment

For the prepared object:

  • (append the preparation method name)
  • MultisetEvaluation
  • MultisetEvaluatorStageB
  • MultisetCache
  • MultisetDungeon - more whimsical

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant