You should "just" need closure combinators:
pub trait Transform<T>: FnMut<(T,)> {
fn map<F: Transform<Self::U>>(self, f: F) -> impl FnMut(T) -> F::Output {
move |item| f(self(item))
}
fn zip<U, F: Transform<U>>(self, f: F) -> impl FnMut((T, U)) -> (Self::Output, F::Output) {
move |(item, iuem)| (self(item), f(iuem))
}
}
impl<T, U, F: FnMut(T) -> U> Transform<T> for F {}
pub use core::convert::identity as start_pipe;
trait Apply<T> {
type Output<U>;
fn apply<F: Transform<T>>(self, f: F) -> Self::Output<F::Output>;
}
impl<I: Iterator> Apply<I::Item> for I {
type Output<U> = impl Iterator<Item=U>;
fn apply<F: Transform<I::Item>>(self, f: F) -> Self::Output<F::Output> {
self.map(f)
}
}
impl<I: Iterator, J: Iterator> Apply<(I::Item, J::Item)> for (I, J) {
type Output<U> = impl Iterator<Item=U>;
fn apply<F: Transform<(I::Item, J::Item)>>(self, f: F) -> Self::Output<(I::Output, J::Output)> {
let (i, j) = self;
i.zip(j).map(f)
}
}
// etc for other monadish types
(entirely untested)