`K` is the one type parameter people change, and it was last. Naming a
history in a struct field meant writing all four to say one thing:
struct Ladder { history: History<i64, ConstantDrift, NullObserver, String> }
struct Analysis<'h> { joint: Joint<'h, i64, ConstantDrift, NullObserver, &'static str> }
Now:
struct Ladder { history: History<String> }
struct Analysis<'h> { joint: Joint<'h> }
`History<K, T, D, O>`, all four defaulted. Bounds may reference later
parameters, so `D: Drift<T> = ConstantDrift` is legal in third position.
`Joint` gains the same defaults, so `Joint<'h, String>` spells it.
72 call sites swapped, and the reorder makes most of them shorter: 18
now read `History<String>` and the `&'static str` ones read `History`.
The two turbofished builders shrink from
`HistoryBuilder::<Untimed, _, _, String>::new()` to
`HistoryBuilder::<String, Untimed>::new()`.
`Joint` keeps `O` structurally, defaulted rather than removed. #72 notes
it never touches the observer, which is true — but it borrows the whole
`&'h History<K, T, D, O>` and calls `History::resolve_terms`, so dropping
the parameter means either a view type or moving that method off
`History`. The default already buys the entire user-visible benefit,
which was the spelling.
Refs #72.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
97 lines
3.1 KiB
Rust
97 lines
3.1 KiB
Rust
//! The traits a consumer needs on the public types, pinned so they cannot be
|
|
//! removed by accident.
|
|
//!
|
|
//! This is written from a consumer's position — deriving `Debug` on a struct
|
|
//! that *holds* a `History` — because that is the thing that failed. Asserting
|
|
//! `History: Debug` in isolation would not have caught the generic-bound half:
|
|
//! `Rating` derives `PartialEq`, but that is only usable if `D: PartialEq`, and
|
|
//! the crate's own only `Drift` impl did not satisfy it.
|
|
|
|
use trueskill_tt::{
|
|
ConstantDrift, ConvergenceOptions, ConvergenceReport, Event, GameOptions, Gaussian, History,
|
|
HistoryBuilder, InferenceError, Member, Outcome, Rating, Team,
|
|
};
|
|
|
|
/// The reported failure, verbatim: a consumer holding a history in app state.
|
|
#[derive(Debug)]
|
|
#[allow(
|
|
dead_code,
|
|
reason = "held only so `derive(Debug)` has something to render"
|
|
)]
|
|
struct App {
|
|
history: History,
|
|
}
|
|
|
|
#[test]
|
|
fn a_struct_holding_a_history_can_derive_debug() {
|
|
let app = App {
|
|
history: History::default(),
|
|
};
|
|
|
|
let rendered = format!("{app:?}");
|
|
|
|
// Summarising, not a dump of every skill store — the same choice `Joint`'s
|
|
// manual `Debug` makes about its n² factorisation.
|
|
assert!(rendered.contains("competitors"), "{rendered}");
|
|
assert!(rendered.contains("time_slices"), "{rendered}");
|
|
assert!(
|
|
!rendered.contains("SkillStore"),
|
|
"History's Debug should summarise, not dump: {rendered}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn history_builder_is_debug_and_clone() {
|
|
let b: HistoryBuilder = History::builder();
|
|
let cloned = b.clone();
|
|
assert!(!format!("{cloned:?}").is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn config_and_input_value_types_are_comparable() {
|
|
assert_eq!(ConstantDrift::new(0.1), ConstantDrift::new(0.1));
|
|
assert_ne!(ConstantDrift::new(0.1), ConstantDrift::new(0.2));
|
|
|
|
assert_eq!(ConvergenceOptions::default(), ConvergenceOptions::default());
|
|
assert_eq!(GameOptions::default(), GameOptions::default());
|
|
|
|
// `Rating: PartialEq` is only reachable through `D: PartialEq`.
|
|
assert_eq!(Rating::<i64, ConstantDrift>::default(), Rating::default());
|
|
assert_ne!(
|
|
Rating::default(),
|
|
Rating::<i64, ConstantDrift>::default().with_drift_scale(2.0)
|
|
);
|
|
|
|
assert_eq!(Member::new("a"), Member::new("a"));
|
|
assert_ne!(Member::new("a"), Member::new("b"));
|
|
assert_eq!(
|
|
Team::with_members([Member::new("a")]),
|
|
Team::with_members([Member::new("a")])
|
|
);
|
|
|
|
let event = || Event {
|
|
time: 1,
|
|
teams: [
|
|
Team::with_members([Member::new("a")]),
|
|
Team::with_members([Member::new("b")]),
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
outcome: Outcome::winner(0, 2),
|
|
};
|
|
assert_eq!(event(), event());
|
|
|
|
assert_eq!(Gaussian::default(), Gaussian::default());
|
|
}
|
|
|
|
#[test]
|
|
fn a_history_is_send_and_sync_and_default() {
|
|
fn assert_send_sync<X: Send + Sync>() {}
|
|
assert_send_sync::<History>();
|
|
assert_send_sync::<InferenceError>();
|
|
|
|
let mut h = History::default();
|
|
let report: ConvergenceReport = h.converge().expect("an empty history converges");
|
|
assert_eq!(report, report.clone());
|
|
}
|