1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
use crate::prelude::*;
/// Sequence extends [`Traversable`] with a function that inverts collection
/// of Applicatives into an Applicative of a collection.
///
/// e.g. `Vec<Result<T, E>>` to `Result<Vec<T>, E>`
///
/// ```
/// use std::fs::{DirEntry, File, FileType, ReadDir};
/// use std::io;
/// use std::path::{Path, PathBuf};
///
/// use naan::prelude::*;
///
/// fn is_dir(ent: &DirEntry) -> bool {
/// ent.file_type()
/// .fmap(|ft: FileType| ft.is_dir())
/// .unwrap_or(false)
/// }
///
/// fn is_file(ent: &DirEntry) -> bool {
/// ent.file_type()
/// .fmap(|ft: FileType| ft.is_file())
/// .unwrap_or(false)
/// }
///
/// /// Recursively search directories and their children
/// /// for all files that match a predicate
/// fn find_all_rec<P: AsRef<Path>, F: Fn(PathBuf) -> io::Result<bool>>(
/// path: P,
/// f: F)
/// -> io::Result<Vec<PathBuf>> {
/// let find_matches = |matches: Vec<io::Result<Vec<PathBuf>>>, ent: DirEntry| {
/// let path_if_found = |found| {
/// if found {
/// vec![ent.path()]
/// } else {
/// vec![]
/// }
/// };
///
/// if is_file(&ent) {
/// matches.append_one(f(ent.path()).fmap(path_if_found))
/// } else if is_dir(&ent) {
/// matches.append_one(find_all_rec(ent.path(), &f))
/// } else {
/// matches
/// }
/// };
///
/// std::fs::read_dir(path).bind1(|dir: ReadDir| {
/// dir.into_iter().collect::<io::Result<Vec<DirEntry>>>()
/// })
/// .bind1(|ents: Vec<DirEntry>| {
/// ents.foldl(find_matches, vec![])
/// .sequence::<hkt::ResultOk<_>>()
/// })
/// .fmap(|vv: Vec<Vec<_>>| vv.concat())
/// }
///
/// /// ...or using try syntax:
/// fn find_all_rec2<P: AsRef<Path>, F: Fn(PathBuf) -> io::Result<bool>>(
/// path: P,
/// f: F)
/// -> io::Result<Vec<PathBuf>> {
/// let find_matches = |matches: Vec<io::Result<Vec<PathBuf>>>, ent: DirEntry| {
/// let path_if_found = |found| {
/// if found {
/// vec![ent.path()]
/// } else {
/// vec![]
/// }
/// };
///
/// if is_file(&ent) {
/// matches.append_one(f(ent.path()).fmap(path_if_found))
/// } else if is_dir(&ent) {
/// matches.append_one(find_all_rec(ent.path(), &f))
/// } else {
/// matches
/// }
/// };
///
/// let dir = std::fs::read_dir(path)?;
/// let ents = dir.into_iter().collect::<io::Result<Vec<DirEntry>>>()?;
/// let out: Vec<Vec<PathBuf>> = ents.foldl(find_matches, Vec::<io::Result<Vec<PathBuf>>>::new())
/// .sequence::<hkt::ResultOk<_>>()?;
///
/// Ok(out.concat())
/// }
/// ```
pub trait Sequence<F, A, TF> {
/// See [`Sequence`]
fn sequence<Ap>(self) -> Ap::T<F::T<A>>
where Self: Sized + Traversable<F, Ap::T<A>, A, TF> + Foldable<F, Ap::T<A>>,
Ap: HKT1,
Ap::T<A>: Applicative<Ap, A> + ApplyOnce<Ap, A>,
Ap::T<TF>: Applicative<Ap, TF> + ApplyOnce<Ap, TF>,
Ap::T<F::T<A>>: Applicative<Ap, F::T<A>> + ApplyOnce<Ap, F::T<A>>,
F: HKT1<T<Ap::T<A>> = Self>
{
self.traversem1::<Ap, _>(|a| a)
}
}
impl<F, A, TF, T> Sequence<F, A, TF> for T {}
/// [`Traversable`]'s signatures that guarantee the traversal
/// function will only ever be called once.
pub trait TraversableOnce<F, A, B, TF>: Traversable<F, A, B, TF> {
/// Traverse a structure with 0 or 1 elements, collecting into
/// an Applicative of 0 or 1 elements.
///
/// ```
/// use std::fs::File;
///
/// use naan::prelude::*;
///
/// fn maybe_path() -> Option<&'static str> {
/// None
/// }
///
/// let tried_read: std::io::Result<Option<File>> =
/// maybe_path().traverse11::<hkt::std::io::Result, _>(File::open);
///
/// assert!(matches!(tried_read, Ok(None)))
/// ```
fn traverse11<Ap, AtoApOfB>(self, f: AtoApOfB) -> Ap::T<F::T<B>>
where Ap: HKT1,
Self: FoldableOnce<F, A>,
Ap::T<B>: Applicative<Ap, B> + ApplyOnce<Ap, B>,
Ap::T<TF>: Applicative<Ap, TF> + ApplyOnce<Ap, TF>,
Ap::T<F::T<B>>: Applicative<Ap, F::T<B>> + ApplyOnce<Ap, F::T<B>>,
AtoApOfB: F1Once<A, Ret = Ap::T<B>>,
F: HKT1<T<A> = Self>;
/// Traverse a structure with 0 or 1 elements, collecting into
/// an Applicative of 0 or more elements.
fn traverse1m<Ap, AtoApOfB>(self, f: AtoApOfB) -> Ap::T<F::T<B>>
where Self: FoldableOnce<F, A>,
Ap: HKT1,
Ap::T<B>: Applicative<Ap, B>,
Ap::T<TF>: Applicative<Ap, TF>,
Ap::T<F::T<B>>: Applicative<Ap, F::T<B>>,
AtoApOfB: F1Once<A, Ret = Ap::T<B>>,
F: HKT1<T<A> = Self>;
/// Aliases [`TraversableOnce::traverse11`]
fn traverse_swap<Ap, AtoApOfB>(self, f: AtoApOfB) -> Ap::T<F::T<B>>
where Ap: HKT1,
Self: Sized + FoldableOnce<F, A>,
Ap::T<B>: Applicative<Ap, B> + ApplyOnce<Ap, B>,
Ap::T<TF>: Applicative<Ap, TF> + ApplyOnce<Ap, TF>,
Ap::T<F::T<B>>: Applicative<Ap, F::T<B>> + ApplyOnce<Ap, F::T<B>>,
AtoApOfB: F1Once<A, Ret = Ap::T<B>>,
F: HKT1<T<A> = Self>
{
self.traverse11::<Ap, AtoApOfB>(f)
}
/// Aliases [`TraversableOnce::traverse1m`]
fn traverse_replicate<Ap, AtoApOfB>(self, f: AtoApOfB) -> Ap::T<F::T<B>>
where Ap: HKT1,
Self: Sized + FoldableOnce<F, A>,
Ap::T<B>: Applicative<Ap, B>,
Ap::T<TF>: Applicative<Ap, TF>,
Ap::T<F::T<B>>: Applicative<Ap, F::T<B>>,
AtoApOfB: F1Once<A, Ret = Ap::T<B>>,
F: HKT1<T<A> = Self>
{
self.traverse1m::<Ap, AtoApOfB>(f)
}
}
/// A Traversable structure is one that can be folded and collected into some [`Applicative`].
///
/// Practically, this allows you to swap nested types, e.g.
/// * apply `FnOnce(T) -> Option<R>` to `Result<T, E>`, yielding `Option<Result<R, E>>`
/// * apply `Fn(T) -> Result<R, E>` to `Vec<T>`, yielding `Result<Vec<R>, E>`
///
/// # Use-cases
/// ## Traverse a structure of length 1 into an Ap of length 1
/// In effect, this swaps the order of types after applying the function.
///
/// e.g. traversing `Option<A>` with a function `Fn(A) -> Result<B, E>` will result in
/// `Result<Option<B>, E>`.
///
/// * `None` -> `Ok(None)` -> `Ok(None)`
/// * `Some(a)` -> `Ok(b)` -> `Ok(Some(b))`
/// * `Some(a)` -> `Err(e)` -> `Err(e)`
///
/// ## Traverse a structure of length 1 into an Ap of length > 1
/// In effect, this will perform a sort of "replication" of the variant wrapping `A`:
///
/// e.g. traversing `Option<A>` with a function `Fn(A) -> Vec<B>` will result in
/// `Vec<Option<B>>`.
///
/// * `None` -> `vec![1, 2, 3]` -> `vec![None, None, None]`
/// * `Some(a)` -> `vec![1, 2, 3]` -> `vec![Some(1), Some(2), Some(3)]`
///
/// ## Traverse a structure of length > 1 into an Ap of length 1
/// By far the most practically useful `traverse` usecase,
/// this allows you to perform an action that returns `Result`, `Option`, etc. and
/// fail as soon as the first `Err`, `None` is encountered.
///
/// e.g. traversing `Vec<A>` with a function `Fn(A) -> Result<B, E>` will result in
/// `Result<Vec<B>>`.
///
/// * `vec![1, 2, 3]` -> `vec![Ok(1), Err(e), ..]` -> `Err(e)`
/// * `vec![a, b, c]` -> `vec![Ok(a), Ok(b), Ok(c)]` -> `Ok(vec![a, b, c])`
///
/// **Note**: the traversal function must be callable multiple times. (must implement [`F1`])
///
/// ## Traverse a structure of length > 1 into an Ap of length > 1
/// Arguably the least useful `traverse` usecase, this performs a cross-product between
/// the two collections.
///
/// <details>
///
/// e.g. traversing `Vec<A>` with a function `Fn(A) -> Vec<B>` will result in
/// a cross-product `Vec<Vec<B>>`.
///
/// 1. Input `vec![2, 4]`
/// 2. Traverse function `f` of `|n| vec![n * 10, n * 20]`
/// 3. Initial output of `Vec::<Vec<B>>::new(Vec::<B>::new())`
/// 4. Intermediate value of `vec![20, 40]` after applying `f` to `2`
/// 5. Map the intermediate to be a vec of _appending actions_ to run against each element of output `vec![append(20), append(40)]`
/// 6. Invoke each append fn on each element in output (currently `vec![vec![]]`) -> `vec![vec![20], vec![40]]`
/// 7. Repeat for each element
/// 8. Observed output is `vec![vec![20, 40], vec![20, 80], vec![40, 40], vec![40, 80]]`
/// </details>
///
/// **Note**: the traversal function must be callable multiple times. (must implement [`F1`])
///
/// **Note**: each value of type `B` appears many times in the return value, so it must be [`Clone`].
///
/// # `traverse` `m1`/`mm`/`1m`/`11`
/// In other languages and environments, `traverse` makes no distinction between the
/// lengths of the Traversable or the [`Applicative`] output, and uses the final usecase mentioned above
/// (_Traverse a structure of length > 1 into an Ap of length > 1_) as the general `traverse`
/// implementation.
///
/// A major consideration is that in Rust is that we must tell the compiler:
/// * when we need to deep-clone values
/// * when we need a function to be callable more than once
///
/// Using a single general traverse implementation would require you to clone
/// values captured in closures (because they must be callable many times) and
/// prevent you from returning many useful types that are not [`Clone`], e.g. [`std::fs::File`].
///
/// In order to provide the best UX around FnOnce-ness and Clone-ness, naan provides
/// specific signatures for each usecase:
///
/// | | Traversable length == 1 | Traversable length > 1 |
/// | - | - | - |
/// | Applicative length == 1 | [`traverse11`](TraversableOnce::traverse11) | [`traversem1`](Traversable::traversem1)|
/// | Applicative length > 1 | [`traverse1m`](TraversableOnce::traverse1m) | [`traversemm`](Traversable::traversemm)|
///
/// Which translates to extra trait requirements:
///
/// | | Traversable length == 1 | Traversable length > 1 |
/// | - | - | - |
/// | Applicative length == 1 | None | function must be [`F1`] |
/// | Applicative length > 1 | None | function must be [`F1`], `B` must be [`Clone`] |
///
/// Additionally, we provide aliases to better describe the problem each signature solves:
/// * `traverse11` -> `traverse_swap`
/// * `traverse1m` -> `traverse_replicate`
/// * `traversem1` -> `traverse`
pub trait Traversable<F, A, B, TF> {
/// Traverse a structure with 0 or more elements, collecting into
/// an Applicative of 0 or 1 elements.
///
/// ```
/// use naan::prelude::*;
///
/// // Mock std::fs::File
/// pub struct File;
/// impl File {
/// pub fn open(path: &str) -> std::io::Result<Self> {
/// if path == "doesnt-exist.txt" {
/// Err(std::io::Error::new(std::io::ErrorKind::NotFound, ""))
/// } else {
/// Ok(Self)
/// }
/// }
/// }
///
/// let paths = vec!["a.txt", "b.txt", "c.txt"];
/// let files: std::io::Result<Vec<File>> = paths.traversem1::<hkt::std::io::Result, _>(File::open);
/// assert!(matches!(files.as_ref().map(|v| v.as_slice()), Ok(&[_, _, _])));
///
/// let paths = vec!["a.txt", "doesnt-exist.txt", "c.txt"];
/// let files: std::io::Result<Vec<File>> = paths.traversem1::<hkt::std::io::Result, _>(File::open);
/// assert!(matches!(files.map_err(|e| e.kind()),
/// Err(std::io::ErrorKind::NotFound)));
/// ```
fn traversem1<Ap, AtoApOfB>(self, f: AtoApOfB) -> Ap::T<F::T<B>>
where Ap: HKT1,
Self: Foldable<F, A>,
Ap::T<B>: Applicative<Ap, B> + ApplyOnce<Ap, B>,
Ap::T<TF>: Applicative<Ap, TF> + ApplyOnce<Ap, TF>,
Ap::T<F::T<B>>: Applicative<Ap, F::T<B>> + ApplyOnce<Ap, F::T<B>>,
AtoApOfB: F1<A, Ret = Ap::T<B>>,
F: HKT1<T<A> = Self>;
/// Traverse a structure with 0 or more elements, collecting into
/// an Applicative of 0 or more elements.
fn traversemm<Ap, AtoApOfB>(self, f: AtoApOfB) -> Ap::T<F::T<B>>
where Ap: HKT1,
B: Clone,
Self: Foldable<F, A>,
Ap::T<B>: Applicative<Ap, B>,
Ap::T<TF>: Applicative<Ap, TF>,
Ap::T<F::T<B>>: Applicative<Ap, F::T<B>>,
AtoApOfB: F1<A, Ret = Ap::T<B>>,
F: HKT1<T<A> = Self>;
/// Aliases [`Traversable::traversem1`]
fn traverse<Ap, AtoApOfB>(self, f: AtoApOfB) -> Ap::T<F::T<B>>
where Ap: HKT1,
Self: Sized + Foldable<F, A>,
Ap::T<B>: Applicative<Ap, B> + ApplyOnce<Ap, B>,
Ap::T<TF>: Applicative<Ap, TF> + ApplyOnce<Ap, TF>,
Ap::T<F::T<B>>: Applicative<Ap, F::T<B>> + ApplyOnce<Ap, F::T<B>>,
AtoApOfB: F1<A, Ret = Ap::T<B>>,
F: HKT1<T<A> = Self>
{
self.traversem1::<Ap, AtoApOfB>(f)
}
}