A `rust` macro for generating unboxed closures
Provides an unbox!
macro for creating unboxed closures
which are nameable in the type system.
unbox_macro
right for me?Possibly! Do you:
macro_rules!
?Then you're in exactly the right place!
In today's Rust, sometimes, small abstractions can take a lot of code.
For instance, suppose you have written a iterator adaptor function that
requires the use of Iterator::map
. Something like:
pub fn zip_with<I,J,F,A,B,C>(iter: I, jitter: J, func: F)
-> ZipWith<I,J,F>
where I: IntoIterator<Item=A>,
J: IntoIterator<Item=B>,
F: FnMut(A,B) -> C,
{
iter.into_iter().zip(jitter.into_iter()).map(|(a,b)| func(a,b))
}
Okey dokey. So what's the return type of that?
pub use std::iter::{Zip,Map};
pub type ZipWith<I,J,F> = Map<Zip<I,J>,F>; // :D
Yeah! Er... not quite.
error: mismatched types [--explain E0308]
--> <anon>:7:9
7 |> iter.into_iter().zip(jitter.into_iter()).map(|(a,b)| f(a,b))
|> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found associated type
note: expected type `std::iter::Map<std::iter::Zip<I, J>, F>`
note: found type `std::iter::Map<std::iter::Zip<<I as std::iter::IntoIterator>::IntoIter, <J as std::iter::IntoIterator>::IntoIter>, [closure@<anon>:7:54: 7:68]>`
Take a close look again at the definition,
because there's a closure in there;
a closure that takes a 2-value tuple and calls a two-argument function.
This is extremely unfortunate for us,
because, as one might imagine, [closure@<anon>:7:54: 7:68]
is not exactly
a valid name in rust.
Long and short: It is impossible to express the return type of this function in any manner!
Possible solution number 1 is to write a lot of code:
use std::iter::Zip;
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Debug,Clone)]
struct ZipWith<I,J,F>{ iter: Zip<I,J>, func: F }
impl<I,J,F,C> Iterator for ZipWith<I,J,F>
where I: Iterator, J: Iterator,
F: FnMut(I::Item, J::Item) -> C,
{
type Item = C;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(a,b)| (&mut self.func)(a,b))
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<I,J,F,C> ExactSizeIterator for ZipWith<I,J,F>
where I: ExactSizeIterator,
J: ExactSizeIterator,
F: FnMut(I::Item,J::Item) -> C,
{ }
impl<I,J,F,C> DoubleEndedIterator for ZipWith<I,J,F>
where I: DoubleEndedIterator + ExactSizeIterator,
J: DoubleEndedIterator + ExactSizeIterator,
F: FnMut(I::Item,J::Item) -> C,
{
#[inline(always)]
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(a,b)| (&mut self.func)(a,b))
}
}
pub fn zip_with<I,J,F,C>(iter: I, jtre: J, func: F) -> ZipWith<I,J,F>
where I: Iterator, J: Iterator,
F: FnMut(I::Item, J::Item) -> C,
{ ZipWith { iter: iter.zip(jtre), func: func } }
(psssst... I hid a bug in there. Can you find it?)
Possible solution number 2 is to enable a bunch of nightly features, and then write a lot of code:
#![feature(unboxed_closures)]
#![feature(fn_traits)]
#![feature(non_ascii_idents)]
use std::iter::{Zip,Map};
/// Hmm, this name made a lot more sense in Haskell...
pub struct Uncurry<F>{ func: F }
pub type ZipWith<I,J,F> = Map<Zip<I,J>, Uncurry<F>>;
impl<F,A,B,C> FnOnce<((A,B),)> for Uncurry<F>
where F: FnMut(A,B) -> C,
{
type Output = C;
extern "rust-call"
fn call_once(mut self, args: ((A,B),)) -> C
{ self.call_mut(args) }
}
impl<F,A,B,C> FnMut<((A,B),)> for Uncurry<F>
where F: FnMut(A,B) -> C,
{
extern "rust-call"
fn call_mut(&mut self, ((a,b),): ((A,B),)) -> C
{ (&mut self.func)(a,b) }
}
pub fn zip_with<I,J,F,C>(iter: I, : J, func: F)
-> ZipWith<I,J,F>
where I: Iterator, J: Iterator,
F: FnMut(I::Item, J::Item) -> C,
{ iter.zip().map(Uncurry{func: func}) }
This crate provides you with a third option: enable a bunch of nightly features, import a crate which contains a lot of code, and then write a smaller(?) amount of code:
#![feature(unboxed_closures)]
#![feature(fn_traits)]
#[macro_use]
extern crate unbox_macro;
use std::iter::{Map,Zip};
pub type ZipWith<I,J,F> = Map<Zip<I,J>, Uncurry<F>>;
unbox!{
pub Generic({F}) Struct(F)
For({A,B,C} where F: FnMut(A,B) -> C)
FnMut Uncurry(&mut self, tuple: (A,B)) -> C {
(&mut self.0)(tuple.0, tuple.1)
}
}
pub fn zip_with<I,J,F,C>(iter: I, jydr: J, func: F)
-> ZipWith<I,J,F>
where I: Iterator, J: Iterator,
F: FnMut(I::Item, J::Item) -> C,
{ iter.zip(jydr).map(Uncurry(func)) }
Generic
and Struct
... this has its own mini-language I have to learn!?Yes. The original plan was really simple:
// I won't show any examples of my original plan because somebody
// quickly scanning the page for examples might see them and think
// they are real examples.
Beautifully simple, right? But then I discovered various flaws:
T:'a
,&'a T
, and thus eitherFnOnce
, or (b) every closed-over typeCopy
.impl
s; without that second set, you can't take a&'b T
argument. (this is why HRTB exists!)[]
, ()
, or {}
. I quickly ran out of usable syntax!<>
are not on this list, and with good reason!)And before I knew it, in order to be able to fit the bare minimum set of features necessary to make it even marginally useful, it ended up with its own mini-lanugage.
Okey dokey:
unbox!
mini-language
(go click that)
unbox!{ Fn Naughty((a,b): (i32,i32)) -> i32 { a + b } }
unbox!{ Fn Nice(tuple: (i32,i32)) -> i32 { tuple.0 + tuple.1 } }
MACROS ARE HARD GUYS
Let's face it. Rust's current macro system is pretty gosh darned weak,
and basically seems to be something to hold people over until the
fabled "macros v2".
This macro is perhaps too ambitious for Rust's macro parser,
and may be better suited to a plugin I mean procedural macro.
Half of what this macro does now was not even possible two weeks prior
to me writing this; it was only at the end of July 2016 that a fix had
finally been committed for the funny business with tt
fragments.
Prior to that fix, I could not have even dreamed of supporting trait
bounds (as it would have entailed actually parsing them!).
go away
Please do! I'm begging you!
YES I'M SORRY OKAY
Box<Iterator>
or &Iterator
?Yes, what about them?
impl Trait
RFC? Soon, we won't need nameable types!In its accepted form,
the minimal impl Trait
RFC
doesn't let you do this:
// Somewhere, in the City of Townsville...
let zipped = zip_with(0..3, 0..4, |x,y| {x+y});
assert_eq!(zipped.rev().len(), 3);
// Meanwhile, in the Town of Citysburg...
let zipped = zip_with(0.., 7.., |x,y| {x+y});
assert_eq!(zipped.next(), Some(7));
because it has no provision for
forwarding conditionally-implemented traits
such as the DoubleEndedIterator
and ExactSizeIterator
traits
seen on many iterator adaptors.
With unbox_macro
, the above snippet works
because zip_with
's return type can be
explicitly defined in terms of Zip
and Map
.
impl FnMut(T) -> U
?
Because...
Because...
Oh.
Well, okay, I guess you can do that if all you really need is a higher-order function, and don't plan to use it to create an iterator. (To date though there still are no provisions in the language for naming the resulting type though, so it's kind of an orthogonal solution to this)
09:34:24 <toby_s> macro_rules! over_you_all { }