tamer: iter::{TryCollect, TryFromIter}: New traits
These traits augment Rust's built-in traits to handle failure scenarios, which will allow us to encapsulate lowering logic into discrete, self-parsing units that enforce e.g. schemas (the example alludes to my intentions).main
parent
1f01833d30
commit
90e3e94c0a
|
@ -48,7 +48,7 @@
|
|||
//! tripping if it encounters an [`Err`].
|
||||
//! This is analogous to a circuit breaker,
|
||||
//! protecting downstream subsystems from faulty data.
|
||||
//!
|
||||
//!
|
||||
//! When a trip happens,
|
||||
//! the [`TripIter`] yields [`None`].
|
||||
//! Downstream systems must determine for themselves whether receiving
|
||||
|
@ -92,7 +92,7 @@
|
|||
//! // We still have access to the iterator where it left off.
|
||||
//! assert_eq!(Some(Ok(1)), values.next());
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! Each of these functions take a callback;
|
||||
//! the [`TripIter`] is valid only for the duration of that function.
|
||||
//! This allows us to know when the caller is finished with the iterator so
|
||||
|
@ -149,7 +149,121 @@
|
|||
//! There are other options,
|
||||
//! but they also result in boilerplate that is handled for you by
|
||||
//! [`TripIter`].
|
||||
//!
|
||||
//!
|
||||
//! Failliable Collecting
|
||||
//! =====================
|
||||
//! [`Iterator::collect`] provides a powerful interface that can remove a
|
||||
//! lot of boilerplate or manual composition,
|
||||
//! but is not designed to handle failures.
|
||||
//! This becomes problematic when attempting to encapsulate failure logic
|
||||
//! within [`FromIterator`] implementations because of foreign traits.
|
||||
//! [`TryFromIterator`] provides a [`Result`] return type for the collection
|
||||
//! itself.
|
||||
//!
|
||||
//! This is useful when an object aggregates data from an iterator in an
|
||||
//! all-or-nothing manner,
|
||||
//! or wishes to reject data it does not support.
|
||||
//!
|
||||
//! Consider this example where we have a set attributes from which we wish
|
||||
//! to generate a `Variable` object consisting of a name and value.
|
||||
//! This object expects both of these attributes to be present,
|
||||
//! and further expects that no other attributes will be provided.
|
||||
//! This effectively defines a basic schema.
|
||||
//!
|
||||
//! ```
|
||||
//! use tamer::iter::{TryCollect, TryFromIterator};
|
||||
//! use tamer::sym::{GlobalSymbolIntern, SymbolId};
|
||||
//! use tamer::sym::st::raw::{L_NAME, L_VALUE};
|
||||
//!
|
||||
//! struct Attr(SymbolId, SymbolId);
|
||||
//!
|
||||
//! #[derive(Debug, PartialEq, Eq)]
|
||||
//! struct Variable {
|
||||
//! name: SymbolId,
|
||||
//! value: SymbolId,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Debug, PartialEq, Eq)]
|
||||
//! enum VariableError {
|
||||
//! UnknownAttr(SymbolId),
|
||||
//! MissingName,
|
||||
//! MissingValue,
|
||||
//! }
|
||||
//!
|
||||
//! impl TryFromIterator<Attr> for Variable {
|
||||
//! type Error = VariableError;
|
||||
//!
|
||||
//! fn try_from_iter<I: IntoIterator<Item = Attr>>(
|
||||
//! iter: I,
|
||||
//! ) -> Result<Self, Self::Error> {
|
||||
//! let mut name = None;
|
||||
//! let mut value = None;
|
||||
//!
|
||||
//! for Attr(attr_name, attr_value) in iter.into_iter() {
|
||||
//! match attr_name {
|
||||
//! L_NAME => { name.replace(attr_value); },
|
||||
//! L_VALUE => { value.replace(attr_value); },
|
||||
//! _ => return Err(VariableError::UnknownAttr(attr_name)),
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! Ok(Variable {
|
||||
//! name: name.ok_or(VariableError::MissingName)?,
|
||||
//! value: value.ok_or(VariableError::MissingValue)?,
|
||||
//! })
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let name = "foo".intern();
|
||||
//! let value = "bar".intern();
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! Ok(Variable { name, value }),
|
||||
//! vec![Attr(L_NAME, name), Attr(L_VALUE, value)].into_iter().try_collect(),
|
||||
//! );
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! Err(VariableError::MissingName),
|
||||
//! vec![Attr(L_VALUE, value)].into_iter().try_collect::<Variable>(),
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! This cannot be done with [`FromIterator`] because it would require
|
||||
//! `impl FromIterator<Attr> for Result<Variable, VariableError>`,
|
||||
//! both of which are foreign traits.
|
||||
//! Perhaps this restriction will be relaxed in Rust in the future,
|
||||
//! rendering [`TryFromIterator`] obsolete.
|
||||
//!
|
||||
//! [`TryFromIterator`] is implemented for all [`FromIterator`],
|
||||
//! allowing [`try_collect`] to accept all types that [`collect`] accepts.
|
||||
//! Type inference maintains the powerful sentient illusion that [`collect`]
|
||||
//! provides:
|
||||
//!
|
||||
//! ```
|
||||
//! use tamer::iter::TryCollect;
|
||||
//!
|
||||
//! // Wraps `Iterator::collect`.
|
||||
//! assert_eq!(
|
||||
//! Ok(Some(vec![1, 2, 3])),
|
||||
//! vec![Some(1), Some(2), Some(3)].into_iter().try_collect()
|
||||
//! );
|
||||
//!
|
||||
//! // Does _not_ unwrap `Result` from `Iterator::collect`.
|
||||
//! assert_eq!(
|
||||
//! Ok(Err("fail")),
|
||||
//! vec![Ok(1), Err("fail")].into_iter().try_collect::<Result<Vec<_>, _>>()
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! Notably, [`try_collect`] _does not_ unwrap [`Result`] from [`collect`].
|
||||
//! This makes sense from a type perspective,
|
||||
//! but may seem unintuitive at a glance.
|
||||
//!
|
||||
//! [`try_collect`]: TryCollect::try_collect
|
||||
//! [`collect`]: Iterator::collect
|
||||
|
||||
mod collect;
|
||||
mod trip;
|
||||
|
||||
/// An [`Iterator`] over [`Result`]s.
|
||||
|
@ -159,4 +273,5 @@ mod trip;
|
|||
/// which is confusing and inconvenient to work with.
|
||||
pub trait ResultIterator<T, E> = Iterator<Item = Result<T, E>>;
|
||||
|
||||
pub use collect::{TryCollect, TryFromIterator};
|
||||
pub use trip::{into_iter_while_ok, with_iter_while_ok, TripIter};
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// Collecting and aggregating iterator values
|
||||
//
|
||||
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
|
||||
//
|
||||
// This file is part of TAME.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A [`FromIterator`] that can fail,
|
||||
//! along with a [`try_collect`](TryCollect::try_collect) that is
|
||||
//! analogous to [`collect`](Iterator::collect).
|
||||
//!
|
||||
//! Rust's built-in [`Iterator::collect`] is a powerful abstraction that is
|
||||
//! able to remove a considerable amount of boilerplate and explicit
|
||||
//! composition,
|
||||
//! but it cannot handle failures.
|
||||
//! These traits attempt to stay as faithful as possible to the Rust
|
||||
//! standard library while permitting (and expecting) failures to occur.
|
||||
//!
|
||||
//! See the [parent module](super) for more information.
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
/// Conversion from an [`Iterator`] that may fail in a controlled way.
|
||||
///
|
||||
/// If the conversion cannot fail,
|
||||
/// use Rust's built-in [`FromIterator`] instead;
|
||||
/// [`TryFromIterator`] is implemented automatically anything
|
||||
/// implementing [`FromIterator`].
|
||||
pub trait TryFromIterator<A>: Sized {
|
||||
/// Conversation failure.
|
||||
type Error;
|
||||
|
||||
/// Attempts to create a value from an iterator,
|
||||
/// failing in a controlled manner.
|
||||
///
|
||||
/// This is generally called through [`TryCollect::try_collect`].
|
||||
///
|
||||
/// See the [module-level documentation](super) for more information.
|
||||
fn try_from_iter<I: IntoIterator<Item = A>>(
|
||||
iter: I,
|
||||
) -> Result<Self, Self::Error>;
|
||||
}
|
||||
|
||||
impl<A, T> TryFromIterator<A> for T
|
||||
where
|
||||
T: FromIterator<A>,
|
||||
{
|
||||
type Error = Infallible;
|
||||
|
||||
/// Create a value from an iterator.
|
||||
///
|
||||
/// This is an automatic implementation for anything implementing
|
||||
/// [`FromIterator`] and cannot fail;
|
||||
/// it directly invokes [`FromIterator::from_iter`].
|
||||
fn try_from_iter<I: IntoIterator<Item = A>>(
|
||||
iter: I,
|
||||
) -> Result<Self, Infallible> {
|
||||
Ok(FromIterator::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
/// Augment [`Iterator`]s with a [`try_collect`](TryCollect::try_collect)
|
||||
/// method,
|
||||
/// which is analogous to [`Iterator::collect`].
|
||||
///
|
||||
/// Where [`Iterator::collect`] uses [`FromIterator`],
|
||||
/// `try_collect` uses [`TryFromIterator`].
|
||||
pub trait TryCollect: Iterator {
|
||||
/// Attempts to transform an iterator into a collection,
|
||||
/// which may fail in a controlled manner under certain
|
||||
/// circumstances.
|
||||
///
|
||||
/// If this operation is infailliable for all relevant types,
|
||||
/// use [`Iterator::collect`] instead.
|
||||
///
|
||||
/// See the [module-level documentation](super) for more information.
|
||||
fn try_collect<B: TryFromIterator<Self::Item>>(self)
|
||||
-> Result<B, B::Error>;
|
||||
}
|
||||
|
||||
impl<I: Iterator> TryCollect for I {
|
||||
fn try_collect<B: TryFromIterator<Self::Item>>(
|
||||
self,
|
||||
) -> Result<B, B::Error> {
|
||||
TryFromIterator::try_from_iter(self)
|
||||
}
|
||||
}
|
|
@ -475,6 +475,7 @@ pub mod st {
|
|||
L_TRUE: cid "true",
|
||||
L_TYPE: cid "type",
|
||||
L_UUROOTPATH: cid "__rootpath",
|
||||
L_VALUE: cid "value",
|
||||
L_WORKSHEET: cid "worksheet",
|
||||
L_XMLNS: cid "xmlns",
|
||||
L_YIELDS: cid "yields",
|
||||
|
|
Loading…
Reference in New Issue