Summary
Strategies.parameter(T) throws NotImplemented by default, alongside id and metadata —
same contract-enforcement pattern for all three
(src/Strategies/contract/abstract_strategy.jl:225-283). But parameter is the odd one out:
"this strategy has no parameter" is a legitimate, common answer, not an oversight. Every
non-parameterized strategy currently in the ecosystem already spells it out explicitly:
Strategies.parameter(::Type{<:CTSolvers.Integrators.SciML}) = nothing # (non-parameterized branch)
Strategies.parameter(::Type{<:CTDirect.Collocation}) = nothing
Strategies.parameter(::Type{<:CTDirect.DirectShooting}) = nothing
Strategies.parameter(::Type{<:CTBase.Differentiation.AbstractADBackend}) = nothing
So nothing is already the fully-established, documented, non-ambiguous answer for "no
parameter" (parameter's own docstring gives parameter(Ipopt) → nothing as the example).
There is no second meaning nothing could be confused with here, so a new sentinel type would
not disambiguate anything real — it would just be a breaking rename of an already-working
convention, for every one of the 8 in-tree strategies above.
The actual gap
Any caller that wants to query parameter on a strategy it did not author — i.e. one that
might not have implemented the (optional) contract — has to catch NotImplemented itself.
OptimalControl does exactly this, in helpers/print.jl:330-343:
function _strategy_parameter(::Type{T}) where {T<:CTBase.Strategies.AbstractStrategy}
return try
CTBase.Strategies.parameter(T)
catch e
e isa CTBase.Exceptions.NotImplemented || rethrow()
nothing
end
end
This is not dead code to simplify away, and tightening the type bound does not remove the
need for it: parameter is optional-to-override on any AbstractStrategy subtype, including
ones not yet written, and Julia has no way to require an interface method at the abstract
type's definition site. A third-party discretizer/modeler/solver that forgets the override
throws regardless of how precisely the caller's own type is bounded.
Nor is there a cleaner alternative already available: hasmethod/applicable cannot
distinguish "concrete type overrode parameter" from "fell through to the AbstractStrategy
default", since the default fallback method always matches the signature. Method-identity
comparison against the fallback would work but is exactly the kind of fragile introspection
that already broke once this session (_strategy_base_name's UnionAll-depth assumption,
#516) — not a pattern worth encouraging a second time.
Requested
A non-throwing accessor living in Strategies itself, next to parameter, so this exact
try/catch is written once — in the package that owns the contract — instead of being
reinvented by every downstream consumer. Two reasonable shapes, either works:
Strategies.parameter(T, default) # get(dict, key, default)-style 2-arg method
Strategies.parameter(T; strict::Bool=true) # keyword opt-out of the throw
Either lets OptimalControl._strategy_parameter collapse to a one-line forward, and gives any
other package walking an AbstractStrategy registry the same safety without copying the
e isa NotImplemented || rethrow() idiom.
Not requested
A NoParameter sentinel type — see above, it would rename a working convention without
resolving any actual ambiguity.
References
Summary
Strategies.parameter(T)throwsNotImplementedby default, alongsideidandmetadata—same contract-enforcement pattern for all three
(
src/Strategies/contract/abstract_strategy.jl:225-283). Butparameteris the odd one out:"this strategy has no parameter" is a legitimate, common answer, not an oversight. Every
non-parameterized strategy currently in the ecosystem already spells it out explicitly:
So
nothingis already the fully-established, documented, non-ambiguous answer for "noparameter" (
parameter's own docstring givesparameter(Ipopt) → nothingas the example).There is no second meaning
nothingcould be confused with here, so a new sentinel type wouldnot disambiguate anything real — it would just be a breaking rename of an already-working
convention, for every one of the 8 in-tree strategies above.
The actual gap
Any caller that wants to query
parameteron a strategy it did not author — i.e. one thatmight not have implemented the (optional) contract — has to catch
NotImplementeditself.OptimalControl does exactly this, in
helpers/print.jl:330-343:This is not dead code to simplify away, and tightening the type bound does not remove the
need for it:
parameteris optional-to-override on anyAbstractStrategysubtype, includingones not yet written, and Julia has no way to require an interface method at the abstract
type's definition site. A third-party discretizer/modeler/solver that forgets the override
throws regardless of how precisely the caller's own type is bounded.
Nor is there a cleaner alternative already available:
hasmethod/applicablecannotdistinguish "concrete type overrode
parameter" from "fell through to theAbstractStrategydefault", since the default fallback method always matches the signature. Method-identity
comparison against the fallback would work but is exactly the kind of fragile introspection
that already broke once this session (
_strategy_base_name'sUnionAll-depth assumption,#516) — not a pattern worth encouraging a second time.
Requested
A non-throwing accessor living in
Strategiesitself, next toparameter, so this exacttry/catch is written once — in the package that owns the contract — instead of being
reinvented by every downstream consumer. Two reasonable shapes, either works:
Either lets
OptimalControl._strategy_parametercollapse to a one-line forward, and gives anyother package walking an
AbstractStrategyregistry the same safety without copying thee isa NotImplemented || rethrow()idiom.Not requested
A
NoParametersentinel type — see above, it would rename a working convention withoutresolving any actual ambiguity.
References