From: DongHun Kwak Date: Wed, 22 Mar 2023 03:48:47 +0000 (+0900) Subject: Import tap 1.0.1 X-Git-Tag: upstream/1.0.1 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fheads%2Fupstream;p=platform%2Fupstream%2Frust-tap.git Import tap 1.0.1 --- ad60aaa1f6b347ae2b28970ed4878edbaae6879b diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..8eb5390 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "f5315f0f5ca90ce6399daac76c1fe3ba645a4e4e" + } +} diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..aa9750d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "tap" +version = "1.0.1" +authors = ["Elliott Linder ", "myrrlyn "] +include = ["Cargo.toml", "README.md", "LICENSE.txt", "src/**/*.rs"] +description = "Generic extensions for tapping values in Rust" +homepage = "https://github.com/myrrlyn/tap" +documentation = "https://docs.rs/tap" +readme = "README.md" +keywords = ["tap", "pipe", "functional", "tap_ok", "tap_some"] +categories = ["no-std", "rust-patterns"] +license = "MIT" +repository = "https://github.com/myrrlyn/tap" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..cf42729 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,30 @@ +[package] +authors = [ + "Elliott Linder ", + "myrrlyn ", +] +categories = [ + "no-std", + "rust-patterns", +] +description = "Generic extensions for tapping values in Rust" +documentation = "https://docs.rs/tap" +homepage = "https://github.com/myrrlyn/tap" +include = [ + "Cargo.toml", + "README.md", + "LICENSE.txt", + "src/**/*.rs", +] +keywords = [ + "tap", + "pipe", + "functional", + "tap_ok", + "tap_some", +] +license = "MIT" +name = "tap" +readme = "README.md" +repository = "https://github.com/myrrlyn/tap" +version = "1.0.1" diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..6d9fc1e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Elliot Linder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3be34d6 --- /dev/null +++ b/README.md @@ -0,0 +1,203 @@ +
+ +# `tap` + +## Suffix-Position Pipeline Behavior + +[![Crate][crate_img]][crate] +[![Documentation][docs_img]][docs] +[![License][license_img]][license_file] + +[![Crate Downloads][downloads_img]][crate] +[![Crate Size][loc_img]][loc] + +
+ +This crate provides extension methods on all types that allow transparent, +temporary, inspection/mutation (tapping), transformation (piping), or type +conversion. These methods make it convenient for you to insert debugging or +modification points into an expression without requiring you to change any other +portions of your code. + +## Example Use + +### Tapping + +You can tap inside a method-chain expression for logging without requiring a +rebind. For instance, you may write a complex expression without any +intermediate debugging steps, and only later decide that you want them. +Ordinarily, this transform would look like this: + +```rust +extern crate reqwest; +extern crate tracing; + +// old +let body = reqwest::blocking::get("https://example.com")? + .text()?; +tracing::debug!("Response contents: {}", body); + +// new, with debugging +let resp = reqwest::blocking::get("https://example.com")?; +tracing::debug!("Response status: {}", resp.status()); +let body = resp.text()?; +tracing::debug!("Response contents: {}", body); +``` + +while with tapping, you can plug the logging statement directly into the overall +expression, without making any other changes: + +```rust +extern crate reqwest; +extern crate tracing; + +let body = reqwest::blocking::get("https://example.com")? + // The only change is the insertion of this line + .tap(|resp| tracing::debug!("Response status: {}", resp.status())) + .text()?; +tracing::debug!("Response contents: {}", body); +``` + +### Mutable Tapping + +Some APIs are written to require mutable borrows, rather than value-to-value +transformations, which can require temporary rebinding in order to create +mutability in an otherwise-immutable context. For example, collecting data into +a vector, sorting the vector, and then freezing it, might look like this: + +```rust +let mut collection = stream().collect::>(); +collection.sort(); +// potential error site: inserting other mutations here +let collection = collection; // now immutable +``` + +But with a mutable tap, you can avoid the duplicate binding *and* guard against +future errors due to the presence of a mutable binding: + +```rust +let collection = stream.collect::>() + .tap_mut(|v| v.sort()); +``` + +The `.tap_mut()` and related methods provide a mutable borrow to their argument, +and allow the final binding site to choose their own level of mutability without +exposing the intermediate permission. + +### Piping + +In addition to transparent inspection or modification points, you may also wish +to use suffix calls for subsequent operations. For example, the standard library +offers the free function `fs::read` to convert `Path`-like objects into +`Vec` of their filesystem contents. Ordinarily, free functions require use +as: + +```rust +use std::fs; + +let mut path = get_base_path(); +path.push("logs"); +path.push(&format!("{}.log", today())); +let contents = fs::read(path)?; +``` + +whereäs use of tapping (for path modification) and piping (for `fs::read`) could +be expressed like this: + +```rust +use std::fs; + +let contents = get_base_path() + .tap_mut(|p| p.push("logs")) + .tap_mut(|p| p.push(&format!("{}.log", today()))) + .pipe(fs::read)?; +``` + +As a clearer example, consider the syntax required to apply multiple free +funtions without `let`-bindings looks like this: + +```rust +let val = last( + third( + second( + first(original_value), + another_arg, + ) + ), + another_arg, +); +``` + +which requires reading the expression in alternating, inside-out, order, to +understand the full sequence of evaluation. With suffix calls, even free +functions can be written in a point-free style that maintains a clear temporal +and syntactic order: + +```rust +let val = original_value + .pipe(first) + .pipe(|v| second(v, another_arg)) + .pipe(third) + .pipe(|v| last(v, another_arg)); +``` + +As piping is an ordinary method, not a syntax transformation, it still requires +that you write function-call expressions when using a function with multiple +arguments in the pipeline. + +### Conversion + +The `conv` module is the simplest: it provides two traits, `Conv` and `TryConv`, +which are sibling traits to `Into` and `TryInto`. Their methods, +`Conv::conv::` and `TryConv::try_conv::`, call the corresponding +trait implementation, and allow you to use `.into()`/`.try_into()` in +non-terminal method calls of an expression. + +```rust +let bytes = "hello".into().into_bytes(); +``` + +does not compile, because Rust cannot decide the type of `"hello".into()`. +Instead of rewriting the expression to use an intermediate `let` binding, you +can write it as + +```rust +let bytes = "hello".conv::().into_bytes(); +``` + +## Full Functionality + +The `Tap` and `Pipe` traits both provide a large number of methods, which use +different parts of the Rust language’s facilities for well-typed value access. +Rather than repeat the API documentation here, you should view the module items +in the [documentation][docs]. + +As a summary, these traits provide methods that, upon receipt of a value, + +- apply no transformation +- apply an `AsRef` or `AsMut` implementation +- apply a `Borrow` or `BorrowMut` implementation +- apply the `Deref` or `DerefMut` implementation + +before executing their effect argument. + +In addition, each `Tap` method `.tap_x` has a sibling method `.tap_x_dbg` that +performs the same work, but only in debug builds; in release builds, the method +call is stripped. This allows you to leave debugging taps in your source code, +without affecting your project’s performance in true usage. + +Lastly, the `tap` module also has traits `TapOptional` and `TapFallible` which +run taps on the variants of `Option` and `Result` enums, respectively, and do +nothing when the variant does not match the method name. `TapOptional::tap_some` +has no effect when called on a `None`, etc. + + +[crate]: https://crates.io/crates/tap "Crate Link" +[crate_img]: https://img.shields.io/crates/v/tap.svg?logo=rust "Crate Page" +[docs]: https://docs.rs/tap "Documentation" +[docs_img]: https://docs.rs/tap/badge.svg "Documentation Display" +[downloads_img]: https://img.shields.io/crates/dv/tap.svg?logo=rust "Crate Downloads" +[license_file]: https://github.com/myrrlyn/tap/blob/master/LICENSE.txt "License File" +[license_img]: https://img.shields.io/crates/l/tap.svg "License Display" +[loc]: https://github.com/myrrlyn/tap "Repository" +[loc_img]: https://tokei.rs/b1/github/myrrlyn/tap?category=code "Repository Size" diff --git a/src/conv.rs b/src/conv.rs new file mode 100644 index 0000000..cec1c85 --- /dev/null +++ b/src/conv.rs @@ -0,0 +1,87 @@ +/*! # Method-Directed Type Conversion + +The `std::convert` module provides traits for converting values from one type to +another. The first of these, [`From`], provides an associated function +[`from(orig: T) -> Self`]. This function can only be called in prefix-position, +as it does not have a `self` receiver. The second, [`Into`], provides a +method [`into(self) -> T`] which *can* be called in suffix-position; due to +intractable problems in the type solver, this method cannot have any *further* +method calls attached to it. It must be bound directly into a `let` or function +call. + +The [`TryFrom`] and [`TryInto`] traits have the same properties, but +permit failure. + +This module provides traits that place the conversion type parameter in the +method, rather than in the trait, so that users can write `.conv::()` to +convert the preceding expression into `T`, without causing any failures in the +type solver. These traits are blanket-implemented on all types that have an +`Into` implementation, which covers both the blanket implementation of `Into` +for types with `From`, and manual implementations of `Into`. + +[`From`]: https://doc.rust-lang.org/std/convert/trait.From.html +[`Into`]: https://doc.rust-lang.org/std/convert/trait.Into.html +[`TryFrom`]: https://doc.rust-lang.org/std/convert/trait.TryFrom.html +[`TryInto`]: https://doc.rust-lang.org/std/convert/trait.TryInto.html +[`from(orig: T) -> Self`]: https://doc.rust-lang.org/std/convert/trait.From.html#tymethod.from +[`into(self) -> T`]: https://doc.rust-lang.org/std/convert/trait.Into.html#tymethod.into +!*/ + +use core::convert::TryInto; + +/// Wraps `Into::::into` as a method that can be placed in pipelines. +pub trait Conv +where + Self: Sized, +{ + /// Converts `self` into `T` using `Into`. + /// + /// # Examples + /// + /// ```rust + /// use tap::conv::Conv; + /// + /// let len = "Saluton, mondo!" + /// .conv::() + /// .len(); + /// ``` + #[inline(always)] + fn conv(self) -> T + where + Self: Into, + T: Sized, + { + Into::::into(self) + } +} + +impl Conv for T {} + +/// Wraps `TryInto::::try_into` as a method that can be placed in pipelines. +pub trait TryConv +where + Self: Sized, +{ + /// Attempts to convert `self` into `T` using `TryInto`. + /// + /// # Examples + /// + /// ```rust + /// use tap::conv::TryConv; + /// + /// let len = "Saluton, mondo!" + /// .try_conv::() + /// .unwrap() + /// .len(); + /// ``` + #[inline(always)] + fn try_conv(self) -> Result + where + Self: TryInto, + T: Sized, + { + TryInto::::try_into(self) + } +} + +impl TryConv for T {} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e6abc40 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,147 @@ +/*! # `tap` – Syntactical Plumb-Lines + +Rust permits functions that take a `self` receiver to be written in “dot-call” +suffix position, rather than the more traditional prefix-position function call +syntax. These functions are restricted to `impl [Trait for] Type` blocks, and +functions anywhere else cannot take advantage of this syntax. + +This crate provides universally-implemented extension traits that permit smooth +suffix-position calls for a handful of common operations: transparent inspection +or modification (tapping), transformation (piping), and type conversion. + +## Tapping + +The [`tap`] module provides the [`Tap`], [`TapOptional`], and [`TapFallible`] +traits. Each of these traits provides methods that take and return a value, and +expose it as a borrow to an effect function. They look like this: + +```rust +use tap::prelude::*; +# struct Tmp; +# fn make_value() -> Tmp { Tmp } +# impl Tmp { fn process_value(self) {} } +# macro_rules! log { ($msg:literal, $val:ident) => {{}}; } + +let end = make_value() + .tap(|v| log!("Produced value: {:?}", v)) + .process_value(); +``` + +These methods are `self -> Self`, and return the value they received without +any transformation. This enables them to be placed anywhere in a larger +expression witohut changing its shape, or causing any semantic changes to the +code. The effect function receives a borrow of the tapped value, optionally run +through the `Borrow`, `AsRef`, or `Deref` view conversions, for the duration of +its execution. + +The effect function cannot return a value, as the tap is incapable of handling +it. + +## Piping + +The [`pipe`] module provides the [`Pipe`] trait. This trait provides methods +that take and transform a value, returning the result of the transformation. +They look like this: + +```rust +use tap::prelude::*; + +struct One; +fn start() -> One { One } +struct Two; +fn end(_: One) -> Two { Two } + +let val: Two = start().pipe(end); + +// without pipes, this would be written as +let _: Two = end(start()); +``` + +These methods are `self -> Other`, and return the value produced by the effect +function. As the methods are always available in suffix position, they can take +as arguments methods that are *not* eligible for dot-call syntax and still place +them as expression suffices. The effect function receives the piped value, +optionally run through the `Borrow`, `AsRef`, or `Deref` view conversions, as +its input, and its output is returned from the pipe. + +For `.pipe()`, the input value is *moved* into the pipe and the effect function, +so the effect function *cannot* return a value whose lifetime depends on the +input value. The other pipe methods all borrow the input value, and may return a +value whose lifetime is tied to it. + +## Converting + +The [`conv`] module provides the [`Conv`] and [`TryConv`] traits. These provide +methods that accept a type parameter on the method name, and forward to the +appropriate `Into` or `TryInto` trait implementation when called. The difference +between `Conv` and `Into` is that `Conv` is declared as `Conv::conv::`, while +`Into` is declared as `Into::::into`. The location of the destination type +parameter makes `.into()` unusable as a non-terminal method call of an +expression, while `.conv::()` can be used as a method call anywhere. + +```rust,compile_fail +let upper = "hello, world" + .into() + .tap_mut(|s| s.make_ascii_uppercase()); +``` + +The above snippet is illegal, because the Rust type solver cannot determine the +type of the sub-expression `"hello, world".into()`, and it will not attempt to +search all available `impl Into for str` implementations to find an `X` which +has a +`fn tap_mut({self, &self, &mut self, Box, Rc, Arc}, _) -> Y` +declared, either as an inherent method or in a trait implemented by `X`, to +resolve the expression. + +Instead, you can write it as + +```rust +use tap::prelude::*; + +let upper = "hello, world" + .conv::() + .tap_mut(|s| s.make_ascii_uppercase()); +``` + +The trait implementation is + +```rust +pub trait Conv: Sized { + fn conv(self) -> T + where Self: Into { + self.into() + } +} +``` + +Each monomorphization of `.conv::()` expands to the appropriate `Into` +implementation, and does nothing else. + +[`Conv`]: conv/trait.Conv.html +[`Pipe`]: pipe/trait.Pipe.html +[`Tap`]: tap/trait.Tap.html +[`TapFallible`]: tap/trait.TapFallible.html +[`TapOptional`]: tap/trait.TapOptional.html +[`TryConv`]: conv/trait.TryConv.html +[`conv`]: conv/index.html +[`pipe`]: pipe/index.html +[`tap`]: tap/index.html +!*/ + +#![no_std] +#![cfg_attr(debug_assertions, warn(missing_docs))] +#![cfg_attr(not(debug_assertions), deny(missing_docs))] + +pub mod conv; +pub mod pipe; +pub mod tap; + +/// Reëxports all traits in one place, for easy import. +pub mod prelude { + #[doc(inline)] + pub use crate::{conv::*, pipe::*, tap::*}; +} + +// also make traits available at crate root +#[doc(inline)] +pub use prelude::*; diff --git a/src/pipe.rs b/src/pipe.rs new file mode 100644 index 0000000..32cba57 --- /dev/null +++ b/src/pipe.rs @@ -0,0 +1,234 @@ +/*! # Universal Suffix Calls + +This module provides a single trait, `Pipe`, which provides a number of methods +useful for placing functions in suffix position. The most common method, `pipe`, +forwards a value `T` into any function `T -> R`, returning `R`. The other +methods all apply some form of borrowing to the value before passing the borrow +into the piped function. These are of less value, but provided to maintain a +similar API to the `tap` module’s methods, and for convenience in the event that +you do have a use for them. + +This module is as much of a [UFCS] method syntax that can be provided as a +library, rather than in the language grammar. + +[UFCS]: https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax +!*/ + +use core::{ + borrow::{Borrow, BorrowMut}, + ops::{Deref, DerefMut}, +}; + +/** Provides universal suffix-position call syntax for any function. + +This trait provides methods that allow any closure or free function to be placed +as a suffix-position call, by writing them as + +```rust +# use tap::pipe::Pipe; +# let receiver = 5; +fn not_a_method(x: i32) -> u8 { x as u8 } +receiver.pipe(not_a_method); +``` + +Piping into functions that take more than one argument still requires writing a +closure with ordinary function-call syntax. This is after all only a library, +not a syntax transformation: + +```rust +use tap::pipe::Pipe; +fn add(x: i32, y: i32) -> i32 { x + y } + +let out = 5.pipe(|x| add(x, 10)); +assert_eq!(out, 15); +``` + +Like tapping, piping is useful for cases where you want to write a sequence of +processing steps without introducing many intermediate bindings, and your steps +contain functions which are not eligible for dot-call syntax. + +The main difference between piping and tapping is that tapping always returns +the value that was passed into the tap, while piping forwards the value into the +effect function, and returns the output of evaluating the effect function with +the value. Piping is a transformation, not merely an inspection or modification. +**/ +pub trait Pipe { + /// Pipes by value. This is generally the method you want to use. + /// + /// # Examples + /// + /// ```rust + /// use tap::pipe::Pipe; + /// + /// fn triple(x: i32) -> i64 { + /// x as i64 * 3 + /// } + /// + /// assert_eq!( + /// 10.pipe(triple), + /// 30, + /// ); + /// ``` + #[inline(always)] + fn pipe(self, func: impl FnOnce(Self) -> R) -> R + where + Self: Sized, + R: Sized, + { + func(self) + } + + /// Borrows `self` and passes that borrow into the pipe function. + /// + /// # Examples + /// + /// ```rust + /// use tap::pipe::Pipe; + /// + /// fn fold(v: &Vec) -> i32 { + /// v.iter().copied().sum() + /// } + /// let vec = vec![1, 2, 3, 4, 5]; + /// let sum = vec.pipe_ref(fold); + /// assert_eq!(sum, 15); + /// assert_eq!(vec.len(), 5); + /// ``` + #[inline(always)] + fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R + where + R: 'a + Sized, + { + func(self) + } + + /// Mutably borrows `self` and passes that borrow into the pipe function. + /// + /// # Examples + /// + /// ```rust + /// use tap::pipe::Pipe; + /// + /// let mut vec = vec![false, true]; + /// let last = vec + /// .pipe_ref_mut(Vec::pop) + /// .pipe(Option::unwrap); + /// assert!(last); + /// ``` + /// + /// Both of these functions are eligible for method-call syntax, and should + /// not be piped. Writing out non-trivial examples for these is a lot of + /// boilerplate. + #[inline(always)] + fn pipe_ref_mut<'a, R>( + &'a mut self, + func: impl FnOnce(&'a mut Self) -> R, + ) -> R + where + R: 'a + Sized, + { + func(self) + } + + /// Borrows `self`, then passes `self.borrow()` into the pipe function. + /// + /// # Examples + /// + /// ```rust + /// use std::borrow::Cow; + /// use tap::pipe::Pipe; + /// + /// let len = Cow::<'static, str>::from("hello, world") + /// .pipe_borrow(str::len); + /// assert_eq!(len, 12); + /// ``` + #[inline(always)] + fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R + where + Self: Borrow, + B: 'a + ?Sized, + R: 'a + Sized, + { + func(Borrow::::borrow(self)) + } + + /// Mutably borrows `self`, then passes `self.borrow_mut()` into the pipe + /// function. + /// + /// ```rust + /// use tap::pipe::Pipe; + /// + /// let mut txt = "hello, world".to_string(); + /// let ptr = txt + /// .pipe_borrow_mut(str::as_mut_ptr); + /// ``` + /// + /// This is a very contrived example, but the `BorrowMut` trait has almost + /// no implementors in the standard library, and of the implementations + /// available, there are almost no methods that fit this API. + #[inline(always)] + fn pipe_borrow_mut<'a, B, R>( + &'a mut self, + func: impl FnOnce(&'a mut B) -> R, + ) -> R + where + Self: BorrowMut, + B: 'a + ?Sized, + R: 'a + Sized, + { + func(BorrowMut::::borrow_mut(self)) + } + + /// Borrows `self`, then passes `self.as_ref()` into the pipe function. + #[inline(always)] + fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R + where + Self: AsRef, + U: 'a + ?Sized, + R: 'a + Sized, + { + func(AsRef::::as_ref(self)) + } + + /// Mutably borrows `self`, then passes `self.as_mut()` into the pipe + /// function. + #[inline(always)] + fn pipe_as_mut<'a, U, R>( + &'a mut self, + func: impl FnOnce(&'a mut U) -> R, + ) -> R + where + Self: AsMut, + U: 'a + ?Sized, + R: 'a + Sized, + { + func(AsMut::::as_mut(self)) + } + + /// Borrows `self`, then passes `self.deref()` into the pipe function. + #[inline(always)] + fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R + where + Self: Deref, + T: 'a + ?Sized, + R: 'a + Sized, + { + func(Deref::deref(self)) + } + + /// Mutably borrows `self`, then passes `self.deref_mut()` into the pipe + /// function. + #[inline(always)] + fn pipe_deref_mut<'a, T, R>( + &'a mut self, + func: impl FnOnce(&'a mut T) -> R, + ) -> R + where + Self: DerefMut + Deref, + T: 'a + ?Sized, + R: 'a + Sized, + { + func(DerefMut::deref_mut(self)) + } +} + +impl Pipe for T where T: ?Sized {} diff --git a/src/tap.rs b/src/tap.rs new file mode 100644 index 0000000..50ce864 --- /dev/null +++ b/src/tap.rs @@ -0,0 +1,587 @@ +/*! # Point-Free Inspection + +The standard library does not provide a way to view or modify an expression +without binding it to a name. This module provides extension methods that take +and return a value, allowing it to be temporarily bound without creating a new +`let`-statement in the enclosing scope. + +The two main uses of these methods are to temporarily attach debugging +tracepoints to an expression without modifying its surrounding code, or to +temporarily mutate an otherwise-immutable object. + +For convenience, methods are available that will modify the *view* of the tapped +object that is passed to the effect function, by using the value’s +`Borrow`/`BorrowMut`, `AsRef`/`AsMut`, or `Index`/`IndexMut` trait +implementations. For example, the `Vec` collection has no `fn sort` method: this +is actually implemented on slices, to which `Vec` dereferences. + +```rust +use tap::tap::*; +# fn make_vec() -> Vec { vec![] } + +// taps take ordinary closures, which can use deref coercion +make_vec().tap_mut(|v| v.sort()); +// `Vec` implements `BorrowMut<[T]>`, +make_vec().tap_borrow_mut(<[_]>::sort); +// and `AsMut<[T]>`, +make_vec().tap_ref_mut(<[_]>::sort); +// and `DerefMut, +make_vec().tap_deref_mut(<[_]>::sort); +// but has no inherent method `sort`. +// make_vec().tap_mut(Vec::sort); +``` +!*/ + +use core::{ + borrow::{Borrow, BorrowMut}, + ops::{Deref, DerefMut}, +}; + +/** Point-free value inspection and modification. + +This trait provides methods that permit viewing the value of an expression +without requiring a new `let` binding or any other alterations to the original +code other than insertion of the `.tap()` call. + +The methods in this trait do not perform any view conversions on the value they +receive; it is borrowed and passed directly to the effect argument. +**/ +pub trait Tap +where + Self: Sized, +{ + /// Immutable access to a value. + /// + /// This function permits a value to be viewed by some inspecting function + /// without affecting the overall shape of the expression that contains this + /// method call. It is useful for attaching assertions or logging points + /// into a multi-part expression. + /// + /// # Examples + /// + /// Here we use `.tap()` to attach logging tracepoints to each stage of a + /// value-processing pipeline. + /// + /// ```rust + /// use tap::tap::Tap; + /// # struct Tmp; + /// # impl Tmp { fn process_value(self) -> Self { self } } + /// # fn make_value() -> Tmp { Tmp } + /// # macro_rules! log { ($msg:literal, $x:ident) => {{}}; } + /// + /// let end = make_value() + /// // this line has no effect on the rest of the code + /// .tap(|v| log!("The produced value was: {}", v)) + /// .process_value(); + /// ``` + #[inline(always)] + fn tap(self, func: impl FnOnce(&Self)) -> Self { + func(&self); + self + } + + /// Mutable access to a value. + /// + /// This function permits a value to be modified by some function without + /// affecting the overall shape of the expression that contains this method + /// call. It is useful for attaching modifier functions that have an + /// `&mut Self -> ()` signature to an expression, without requiring an + /// explicit `let mut` binding. + /// + /// # Examples + /// + /// Here we use `.tap_mut()` to sort an array without requring multiple + /// bindings. + /// + /// ```rust + /// use tap::tap::Tap; + /// + /// let sorted = [1i32, 5, 2, 4, 3] + /// .tap_mut(|arr| arr.sort()); + /// assert_eq!(sorted, [1, 2, 3, 4, 5]); + /// ``` + /// + /// Without tapping, this would be written as + /// + /// ```rust + /// let mut received = [1, 5, 2, 4, 3]; + /// received.sort(); + /// let sorted = received; + /// ``` + /// + /// The mutable tap is a convenient alternative when the expression to + /// produce the collection is more complex, for example, an iterator + /// pipeline collected into a vector. + #[inline(always)] + fn tap_mut(mut self, func: impl FnOnce(&mut Self)) -> Self { + func(&mut self); + self + } + + /// Immutable access to the `Borrow` of a value. + /// + /// This function is identcal to [`Tap::tap`], except that the effect + /// function recevies an `&B` produced by `Borrow::::borrow`, rather than + /// an `&Self`. + /// + /// [`Tap::tap`]: trait.Tap.html#method.tap + #[inline(always)] + fn tap_borrow(self, func: impl FnOnce(&B)) -> Self + where + Self: Borrow, + B: ?Sized, + { + func(Borrow::::borrow(&self)); + self + } + + /// Mutable access to the `BorrowMut` of a value. + /// + /// This function is identical to [`Tap::tap_mut`], except that the effect + /// function receives an `&mut B` produced by `BorrowMut::::borrow_mut`, + /// rather than an `&mut Self`. + /// + /// [`Tap::tap_mut`]: trait.Tap.html#method.tap_mut + #[inline(always)] + fn tap_borrow_mut(mut self, func: impl FnOnce(&mut B)) -> Self + where + Self: BorrowMut, + B: ?Sized, + { + func(BorrowMut::::borrow_mut(&mut self)); + self + } + + /// Immutable access to the `AsRef` view of a value. + /// + /// This function is identical to [`Tap::tap`], except that the effect + /// function receives an `&R` produced by `AsRef::::as_ref`, rather than + /// an `&Self`. + /// + /// [`Tap::tap`]: trait.Tap.html#method.tap + #[inline(always)] + fn tap_ref(self, func: impl FnOnce(&R)) -> Self + where + Self: AsRef, + R: ?Sized, + { + func(AsRef::::as_ref(&self)); + self + } + + /// Mutable access to the `AsMut` view of a value. + /// + /// This function is identical to [`Tap::tap_mut`], except that the effect + /// function receives an `&mut R` produced by `AsMut::::as_mut`, rather + /// than an `&mut Self`. + /// + /// [`Tap::tap_mut`]: trait.Tap.html#method.tap_mut + #[inline(always)] + fn tap_ref_mut(mut self, func: impl FnOnce(&mut R)) -> Self + where + Self: AsMut, + R: ?Sized, + { + func(AsMut::::as_mut(&mut self)); + self + } + + /// Immutable access to the `Deref::Target` of a value. + /// + /// This function is identical to [`Tap::tap`], except that the effect + /// function receives an `&Self::Target` produced by `Deref::deref`, rather + /// than an `&Self`. + /// + /// [`Tap::tap`]: trait.Tap.html#method.tap + #[inline(always)] + fn tap_deref(self, func: impl FnOnce(&T)) -> Self + where + Self: Deref, + T: ?Sized, + { + func(Deref::deref(&self)); + self + } + + /// Mutable access to the `Deref::Target` of a value. + /// + /// This function is identical to [`Tap::tap_mut`], except that the effect + /// function receives an `&mut Self::Target` produced by + /// `DerefMut::deref_mut`, rather than an `&mut Self`. + /// + /// [`Tap::tap_mut`]: trait.Tap.html#method.tap_mut + #[inline(always)] + fn tap_deref_mut(mut self, func: impl FnOnce(&mut T)) -> Self + where + Self: DerefMut + Deref, + T: ?Sized, + { + func(DerefMut::deref_mut(&mut self)); + self + } + + // debug-build-only copies of the above methods + + /// Calls `.tap()` only in debug builds, and is erased in release builds. + #[inline(always)] + fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self { + if cfg!(debug_assertions) { + func(&self); + } + self + } + + /// Calls `.tap_mut()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_mut_dbg(mut self, func: impl FnOnce(&mut Self)) -> Self { + if cfg!(debug_assertions) { + func(&mut self); + } + self + } + + /// Calls `.tap_borrow()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_borrow_dbg(self, func: impl FnOnce(&B)) -> Self + where + Self: Borrow, + B: ?Sized, + { + if cfg!(debug_assertions) { + func(Borrow::::borrow(&self)); + } + self + } + + /// Calls `.tap_borrow_mut()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_borrow_mut_dbg(mut self, func: impl FnOnce(&mut B)) -> Self + where + Self: BorrowMut, + B: ?Sized, + { + if cfg!(debug_assertions) { + func(BorrowMut::::borrow_mut(&mut self)); + } + self + } + + /// Calls `.tap_ref()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_ref_dbg(self, func: impl FnOnce(&R)) -> Self + where + Self: AsRef, + R: ?Sized, + { + if cfg!(debug_assertions) { + func(AsRef::::as_ref(&self)); + } + self + } + + /// Calls `.tap_ref_mut()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_ref_mut_dbg(mut self, func: impl FnOnce(&mut R)) -> Self + where + Self: AsMut, + R: ?Sized, + { + if cfg!(debug_assertions) { + func(AsMut::::as_mut(&mut self)); + } + self + } + + /// Calls `.tap_deref()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_deref_dbg(self, func: impl FnOnce(&T)) -> Self + where + Self: Deref, + T: ?Sized, + { + if cfg!(debug_assertions) { + func(Deref::deref(&self)); + } + self + } + + /// Calls `.tap_deref_mut()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_deref_mut_dbg(mut self, func: impl FnOnce(&mut T)) -> Self + where + Self: DerefMut + Deref, + T: ?Sized, + { + if cfg!(debug_assertions) { + func(DerefMut::deref_mut(&mut self)); + } + self + } +} + +impl Tap for T where T: Sized {} + +/** Optional tapping, conditional on the optional presence of a value. + +This trait is intended for use on types that express the concept of “optional +presence”, primarily the [`Option`] monad. It provides taps that inspect the +container to determine if the effect function should execute or not. + +> Note: This trait is a specialization of [`TapFallible`], and exists because +> the [`std::ops::Try`] trait is still unstable. When `Try` stabilizes, this +> trait can be removed, and `TapFallible` blanket-applied to all `Try` +> implementors. + +[`Option`]: https://doc.rust-lang.org/std/option/enum.Option.html +[`TapFallible`]: trait.TapFallible.html +[`std::ops::Try`]: https://doc.rust-lang.org/std/ops/trait.Try.html +**/ +pub trait TapOptional +where + Self: Sized, +{ + /// The interior type that the container may or may not carry. + type Val: ?Sized; + + /// Immutabily accesses an interior value only when it is present. + /// + /// This function is identical to [`Tap::tap`], except that it is required + /// to check the implementing container for value presence before running. + /// Implementors must not run the effect function if the container is marked + /// as being empty. + /// + /// [`Tap::tap`]: trait.Tap.html#method.tap + fn tap_some(self, func: impl FnOnce(&Self::Val)) -> Self; + + /// Mutably accesses an interor value only when it is present. + /// + /// This function is identical to [`Tap::tap_mut`], except that it is + /// required to check the implementing container for value presence before + /// running. Implementors must not run the effect function if the container + /// is marked as being empty. + /// + /// [`Tap::tap_mut`]: trait.Tap.html#method.tap_mut + fn tap_some_mut(self, func: impl FnOnce(&mut Self::Val)) -> Self; + + /// Runs an effect function when the container is empty. + /// + /// This function is identical to [`Tap::tap`], except that it is required + /// to check the implementing container for value absence before running. + /// Implementors must not run the effect function if the container is marked + /// as being non-empty. + /// + /// [`Tap::tap`]: trait.Tap.html#method.tap + fn tap_none(self, func: impl FnOnce()) -> Self; + + /// Calls `.tap_some()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_some_dbg(self, func: impl FnOnce(&Self::Val)) -> Self { + if cfg!(debug_assertions) { + self.tap_some(func) + } else { + self + } + } + + /// Calls `.tap_some_mut()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_some_mut_dbg(self, func: impl FnOnce(&mut Self::Val)) -> Self { + if cfg!(debug_assertions) { + self.tap_some_mut(func) + } else { + self + } + } + + /// Calls `.tap_none()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_none_dbg(self, func: impl FnOnce()) -> Self { + if cfg!(debug_assertions) { + self.tap_none(func) + } else { + self + } + } +} + +impl TapOptional for Option { + type Val = T; + + #[inline(always)] + fn tap_some(self, func: impl FnOnce(&T)) -> Self { + if let Some(ref val) = self { + func(val); + } + self + } + + #[inline(always)] + fn tap_some_mut(mut self, func: impl FnOnce(&mut T)) -> Self { + if let Some(ref mut val) = self { + func(val); + } + self + } + + #[inline(always)] + fn tap_none(self, func: impl FnOnce()) -> Self { + if self.is_none() { + func(); + } + self + } +} + +/** Fallible tapping, conditional on the optional success of an expression. + +This trait is intended for use on types that express the concept of “fallible +presence”, primarily the [`Result`] monad. It provides taps that inspect the +container to determine if the effect function should execute or not. + +> Note: This trait would ideally be implemented as a blanket over all +> [`std::ops::Try`] implementors. When `Try` stabilizes, this crate can be +> updated to do so. + +[`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html +[`std::ops::Try`]: https://doc.rust-lang.org/std/ops/trait.Try.html +**/ +pub trait TapFallible +where + Self: Sized, +{ + /// The interior type used to indicate a successful construction. + type Ok: ?Sized; + + /// The interior type used to indicate a failed construction. + type Err: ?Sized; + + /// Immutably accesses an interior success value. + /// + /// This function is identical to [`Tap::tap`], except that it is required + /// to check the implementing container for value success before running. + /// Implementors must not run the effect function if the container is marked + /// as being a failure. + /// + /// [`Tap::tap`]: trait.Tap.html#method.tap + fn tap_ok(self, func: impl FnOnce(&Self::Ok)) -> Self; + + /// Mutably accesses an interior success value. + /// + /// This function is identical to [`Tap::tap_mut`], except that it is + /// required to check the implementing container for value success before + /// running. Implementors must not run the effect function if the container + /// is marked as being a failure. + /// + /// [`Tap::tap_mut`]: trait.Tap.html#method.tap_mut + fn tap_ok_mut(self, func: impl FnOnce(&mut Self::Ok)) -> Self; + + /// Immutably accesses an interior failure value. + /// + /// This function is identical to [`Tap::tap`], except that it is required + /// to check the implementing container for value failure before running. + /// Implementors must not run the effect function if the container is marked + /// as being a success. + /// + /// [`Tap::tap`]: trait.Tap.html#method.tap + fn tap_err(self, func: impl FnOnce(&Self::Err)) -> Self; + + /// Mutably accesses an interior failure value. + /// + /// This function is identical to [`Tap::tap_mut`], except that it is + /// required to check the implementing container for value failure before + /// running. Implementors must not run the effect function if the container + /// is marked as being a success. + /// + /// [`Tap::tap_mut`]: trait.Tap.html#method.tap_mut + fn tap_err_mut(self, func: impl FnOnce(&mut Self::Err)) -> Self; + + /// Calls `.tap_ok()` only in debug builds, and is erased in release builds. + #[inline(always)] + fn tap_ok_dbg(self, func: impl FnOnce(&Self::Ok)) -> Self { + if cfg!(debug_assertions) { + self.tap_ok(func) + } else { + self + } + } + + /// Calls `.tap_ok_mut()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_ok_mut_dbg(self, func: impl FnOnce(&mut Self::Ok)) -> Self { + if cfg!(debug_assertions) { + self.tap_ok_mut(func) + } else { + self + } + } + + /// Calls `.tap_err()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_err_dbg(self, func: impl FnOnce(&Self::Err)) -> Self { + if cfg!(debug_assertions) { + self.tap_err(func) + } else { + self + } + } + + /// Calls `.tap_err_mut()` only in debug builds, and is erased in release + /// builds. + #[inline(always)] + fn tap_err_mut_dbg(self, func: impl FnOnce(&mut Self::Err)) -> Self { + if cfg!(debug_assertions) { + self.tap_err_mut(func) + } else { + self + } + } +} + +impl TapFallible for Result { + type Ok = T; + type Err = E; + + #[inline(always)] + fn tap_ok(self, func: impl FnOnce(&T)) -> Self { + if let Ok(ref val) = self { + func(val); + } + self + } + + #[inline(always)] + fn tap_ok_mut(mut self, func: impl FnOnce(&mut T)) -> Self { + if let Ok(ref mut val) = self { + func(val); + } + self + } + + #[inline(always)] + fn tap_err(self, func: impl FnOnce(&E)) -> Self { + if let Err(ref val) = self { + func(val); + } + self + } + + #[inline(always)] + fn tap_err_mut(mut self, func: impl FnOnce(&mut E)) -> Self { + if let Err(ref mut val) = self { + func(val); + } + self + } +}