Skip to content

feat(strategies): non-throwing parameter(T, default) accessor - #521

Merged
ocots merged 1 commit into
mainfrom
feat/parameter-default-518
Jul 29, 2026
Merged

feat(strategies): non-throwing parameter(T, default) accessor#521
ocots merged 1 commit into
mainfrom
feat/parameter-default-518

Conversation

@ocots

@ocots ocots commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds Strategies.parameter(strategy_type, default), the non-throwing counterpart to parameter(strategy_type) — the same relationship as get(dict, key, default) to dict[key].
  • default is only substituted when the underlying call throws Exceptions.NotImplemented; a strategy that legitimately implements parameter and returns nothing still returns nothing, not default, and any other exception still propagates unchanged.
  • This lets any caller walking an AbstractStrategy it did not author (and cannot guarantee implements the optional parameter contract) query it safely, instead of hand-rolling try/catch e; e isa NotImplemented || rethrow(); nothing end — which is exactly what OptimalControl does today in helpers/print.jl.

Fixes #518

On the issue's two proposed shapes

The issue offered parameter(T, default) or parameter(T; strict::Bool=true), "either works". They're not actually equivalent in Julia: keyword arguments are resolved only after dispatch on positional types, against whichever method that dispatch selects. Every currently-implemented strategy overrides parameter with its own plain 1-arg method (no keyword declared), so the keyword form would throw MethodError: no keyword argument strict for every strategy that already implements the contract correctly, and only work for the ones that fall through to the abstract default (the ones that don't need the safety net in the first place). The 2-arg method has no such issue — it wraps a call to parameter(T) internally and works uniformly regardless of which concrete method that inner call resolves to. Went with the 2-arg form for this reason.

Test plan

  • New testset in test/suite/strategies/test_abstract_strategy.jl:
    • falls back to default when parameter isn't implemented at all
    • a strategy returning a legitimate nothing is not conflated with the "unimplemented" case
    • a real parameter value passes through unchanged
    • a non-NotImplemented exception still propagates (not swallowed)
    • the 1-arg form's throwing behavior is unchanged
  • suite/strategies/test_abstract_strategy.jl (90 tests) green
  • Full suite green: 4962/4962

🤖 Generated with Claude Code

Strategies.parameter(T) throws NotImplemented by default, same as id and
metadata. But "no parameter" is a legitimate, already-established answer
(nothing) for parameter specifically, not an oversight the way a missing id
or metadata would be. The real gap is callers that query parameter on a
strategy type they did not author and cannot guarantee has overridden the
(optional) contract - they had to catch NotImplemented themselves, as
OptimalControl currently does by hand in helpers/print.jl.

Adds parameter(strategy_type, default), the non-throwing counterpart to
parameter(strategy_type) - the same relationship as get(dict, key, default)
to dict[key]. default is only substituted when the underlying call throws
NotImplemented; a strategy that legitimately implements parameter and
returns nothing still returns nothing, not default, and any other exception
still propagates.

Implemented as a single get-style 2-arg method rather than the issue's
alternative keyword-based shape (parameter(T; strict=false)): Julia
resolves keyword arguments only after dispatching on positional types,
against whichever method that dispatch selects. Every currently-implemented
strategy overrides parameter with its own plain 1-arg method with no
keyword declared, so the keyword form would throw "no keyword argument
strict" for every strategy that already implements the contract correctly,
and only work for the ones that don't. The 2-arg method has no such issue:
it wraps a call to parameter(T) internally and works uniformly regardless
of which concrete method that inner call resolves to.

Fixes #518

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ocots
ocots merged commit 58f30ee into main Jul 29, 2026
3 checks passed
@ocots ocots mentioned this pull request Jul 30, 2026
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

Successfully merging this pull request may close these issues.

Add a non-throwing Strategies.parameter accessor, so consumers stop reinventing the NotImplemented catch

1 participant