Skip to main content

dataset_ml/
traits.rs

1//! The [`MlDataset`] trait shared by every loader in this crate.
2//!
3//! Every loader parses into a [`Table`], so the trait needs no type parameter and
4//! no per-loader accessor. It covers the container operations that stay the same
5//! whatever the table holds:
6//!
7//! - [`invalidate`](MlDataset::invalidate) drops the in-memory cache and forces the
8//!   next access to re-read (and re-verify) the file on disk.
9//! - [`is_loaded`](MlDataset::is_loaded) and [`storage_dir`](MlDataset::storage_dir)
10//!   let you inspect a loader without touching the data.
11//! - [`n_samples`](MlDataset::n_samples) gives the sample count.
12//!
13//! This trait names its data accessors [`load`](MlDataset::load),
14//! [`peek`](MlDataset::peek), and [`unload`](MlDataset::unload) rather than reusing
15//! `data`, `get_data`, and `take_data`. This way, a trait method never silently
16//! shadows the inherent method of the same name, and the inherent method never
17//! shadows the trait method either. Both sets are always available and always
18//! agree. Use whichever reads better where you are.
19//!
20//! This module is always available. The `dataset` feature only decides whether the
21//! built-in loaders that implement the trait compile with it.
22//!
23//! # Example
24//!
25//! This example needs the `dataset` feature, because it uses two built-in loaders.
26//!
27//! ```no_run
28//! use dataset_ml::traits::MlDataset;
29//! use dataset_ml::{Iris, SmsSpam};
30//!
31//! // One function works for any loader, including the text corpora, whose table
32//! // holds entirely different columns from Iris's.
33//! fn describe<D: MlDataset>(dataset: &D) -> String {
34//!     format!("{} ({} samples)", D::NAME, dataset.n_samples().unwrap())
35//! }
36//!
37//! assert_eq!(describe(&Iris::new("./data")), "iris (150 samples)");
38//! assert_eq!(describe(&SmsSpam::new("./data")), "sms_spam (5574 samples)");
39//! ```
40
41use crate::table::Table;
42use dataset_core::{Dataset, DatasetError};
43
44/// The lazy-loading behavior every dataset loader in this crate shares.
45///
46/// Implementors wrap a [`Dataset<Table, DatasetError>`](dataset_core::Dataset) and
47/// only need to expose it through the three needed methods. The trait provides
48/// everything else.
49///
50/// # Implementing it for your own loader
51///
52/// ```rust
53/// use dataset_core::{Dataset, DatasetError};
54/// use dataset_ml::table::Table;
55/// use dataset_ml::traits::MlDataset;
56///
57/// struct MyDataset {
58///     dataset: Dataset<Table, DatasetError>,
59/// }
60///
61/// impl MlDataset for MyDataset {
62///     const NAME: &'static str = "my_dataset";
63///
64///     fn dataset(&self) -> &Dataset<Table, DatasetError> {
65///         &self.dataset
66///     }
67///
68///     fn dataset_mut(&mut self) -> &mut Dataset<Table, DatasetError> {
69///         &mut self.dataset
70///     }
71///
72///     fn into_dataset(self) -> Dataset<Table, DatasetError> {
73///         self.dataset
74///     }
75/// }
76/// ```
77pub trait MlDataset: Sized {
78    /// The dataset's identifier, matching the one used in its error messages
79    /// (for example, `"iris"`, `"sms_spam"`).
80    const NAME: &'static str;
81
82    /// Borrow the underlying container.
83    fn dataset(&self) -> &Dataset<Table, DatasetError>;
84
85    /// Borrow the underlying container mutably.
86    fn dataset_mut(&mut self) -> &mut Dataset<Table, DatasetError>;
87
88    /// Consume the loader and return the underlying container.
89    fn into_dataset(self) -> Dataset<Table, DatasetError>;
90
91    /// Load the dataset if needed and borrow the table.
92    ///
93    /// The generic equivalent of each loader's inherent `data()`: it downloads and
94    /// parses on first call, then returns the cached value. Concurrent calls run
95    /// the loader once and share the result.
96    ///
97    /// # Errors
98    ///
99    /// Returns `DatasetError` if the download, file I/O, or parsing fails.
100    fn load(&self) -> Result<&Table, DatasetError> {
101        self.dataset().load()
102    }
103
104    /// Load the dataset if needed and borrow the table **mutably**.
105    ///
106    /// If you edit the table in place through the returned reference, the change
107    /// persists in the cache, so later accesses observe it. Unlike the inherent
108    /// `get_data_mut()`, this loads rather than returning `None` when nothing is
109    /// cached yet.
110    ///
111    /// # Errors
112    ///
113    /// Returns `DatasetError` if the download, file I/O, or parsing fails.
114    fn load_mut(&mut self) -> Result<&mut Table, DatasetError> {
115        self.dataset_mut().load_mut()
116    }
117
118    /// Borrow the table **without** triggering loading.
119    ///
120    /// The generic equivalent of each loader's inherent `get_data()`. Returns
121    /// `None`, rather than downloading, when the dataset has not loaded yet.
122    fn peek(&self) -> Option<&Table> {
123        self.dataset().get()
124    }
125
126    /// Move the table out, leaving the loader reusable and unloaded.
127    ///
128    /// The generic equivalent of each loader's inherent `take_data()`, except that
129    /// it never loads: it returns `None` if nothing is cached.
130    fn unload(&mut self) -> Option<Table> {
131        self.dataset_mut().take()
132    }
133
134    /// Whether the cache currently holds the table.
135    ///
136    /// Never triggers loading, so this is the cheap way to ask whether an accessor
137    /// already has the value or must start a download.
138    fn is_loaded(&self) -> bool {
139        self.dataset().is_loaded()
140    }
141
142    /// The directory this loader stores its files in.
143    fn storage_dir(&self) -> &str {
144        self.dataset().storage_dir()
145    }
146
147    /// Drop the cached table, keeping the loader usable.
148    ///
149    /// The next access re-reads the file from `storage_dir`. This re-runs the
150    /// SHA-256 check and the parser, and re-downloads if the file is gone or no
151    /// longer matches. Use it to reclaim the memory a dataset occupies
152    /// (`covtype`, `kddcup99`), or to read a file that changed on disk.
153    ///
154    /// To retrieve the table rather than discard it, use [`unload`](Self::unload).
155    fn invalidate(&mut self) {
156        self.dataset_mut().invalidate();
157    }
158
159    /// The number of samples in the dataset, loading it if needed.
160    ///
161    /// Every column of the table holds this many values.
162    ///
163    /// # Errors
164    ///
165    /// Returns `DatasetError` if the download, file I/O, or parsing fails.
166    fn n_samples(&self) -> Result<usize, DatasetError> {
167        Ok(self.load()?.n_samples())
168    }
169}
170
171/// Implement [`MlDataset`] for a loader that stores its container in a field named
172/// `dataset`.
173///
174/// Every loader in this crate has that exact shape, so the implementation is
175/// entirely mechanical. This macro writes the three needed methods and leaves the
176/// rest to the trait's defaults. It is crate-internal. Downstream loaders
177/// implement the trait directly (see [`MlDataset`]'s own example).
178#[cfg(feature = "dataset")]
179macro_rules! impl_ml_dataset {
180    ($struct_name:ident, $name:literal) => {
181        impl $crate::traits::MlDataset for $struct_name {
182            const NAME: &'static str = $name;
183
184            fn dataset(
185                &self,
186            ) -> &::dataset_core::Dataset<$crate::table::Table, ::dataset_core::DatasetError> {
187                &self.dataset
188            }
189
190            fn dataset_mut(
191                &mut self,
192            ) -> &mut ::dataset_core::Dataset<$crate::table::Table, ::dataset_core::DatasetError>
193            {
194                &mut self.dataset
195            }
196
197            fn into_dataset(
198                self,
199            ) -> ::dataset_core::Dataset<$crate::table::Table, ::dataset_core::DatasetError> {
200                self.dataset
201            }
202        }
203    };
204}
205
206#[cfg(feature = "dataset")]
207pub(crate) use impl_ml_dataset;