Macro tuplez::mapper

source ·
macro_rules! mapper {
    ($($x:ident $(, $lt:lifetime)*: $it:ty $(=> $ot:ty)? : $e:expr);* $(;)?) => { ... };
    ($($x:ident $(, $lt:lifetime)*: $it:ty $(=> $ot:ty)? : $e:expr ;)*
        $(_ $(=> $ot2:ty)? : $f:ident $(where $($t:tt)*)?)?) => { ... };
    (@impl _ : $f:ident $(where $($t:tt)*)?) => { ... };
    (@impl _ => $ot:ty : $f:ident $(where $($t:tt)*)?) => { ... };
    (@impl $x:ident : $it:ty : $e:expr) => { ... };
    (@impl $x:ident $(, $lt:lifetime)* : $it:ty : $e:expr) => { ... };
    (@impl $x:ident : $it:ty => $ot:ty : $e:expr) => { ... };
    (@impl $x:ident $(, $lt:lifetime)* : $it:ty => $ot:ty : $e:expr) => { ... };
}
Expand description

Provides a simple way to create a functor that implements Mapper.

§Syntax

Introduce   = Variable [, Lifetime1, Lifetime2, ... ]
TypeMap     = TypeIn [=> TypeOut]
Specialized = Introduce : TypeMap : Expr
Universal   = _ : TypeMap : Function [where Bounds]

mapper!([ Specialized1; Specialized2; ... ] [; Univsersal])

The [ and ] markers only indicate optional content but not that the [ and ] need to be input.

Similarly, ... indicates several repeated segments, rather than inputing ....

§Explanation

§Specialized mapping rules

Firstly introduce a variable name used to represent the element (Recommand using x) and all possible lifetime paramters. If the element type contains a reference or generic lifetime parameter, please explicitly annotate all lifetimes:

x, 'a

Then specify the mapping of the type, that is, element type => output type. If the element type and output type are the same type, the output type can be omitted:

&'a str => &'a [u8]

Next, write down the conversion expression, remember that x is an immutable reference to the element:

x.as_bytes()

Finally, use : to link them, and a specialized mapping rule is complete:

x, 'a : &'a str => &'a [u8] : x.as_bytes()

Construct specialized mapping rules for all element types in the tuple, and then combine them in a mapper! macro to traverse the tuple:

use tuplez::*;

let tup = tuple!(1, "hello", 3.14).foreach(mapper!{
   x: i32 : *x + 1;                                 // Omit the output type
   x: f32 => String: x.to_string();
   x, 'a: &'a str => &'a [u8]: x.as_bytes()
});
assert_eq!(tup, tuple!(2, b"hello" as &[u8], "3.14".to_string()));

§Universal mapping rule

Universal mapping rule currently only supports conversion through generic functions, not expressions. So first define the mapping function:

fn to_string<T: ToString>(v: &T) -> String {
    v.to_string()
}

Write down the mapping of types as before, only this time we use an _ for element types.

_ => String

Finally, use : to link them. But there is a little noise here. We need to write down the bounds of the mapping function as well:

_ => String : to_string where ToString

Put it in the mapper! macro to traverse the tuple:

use tuplez::*;

fn to_string<T: ToString>(v: &T) -> String {
    v.to_string()
}

let tup = tuple!(1, "hello", 3.14);
let tup2 = tup.foreach(mapper! {
    _ => String: to_string where ToString
});
assert_eq!(
    tup2,
    tuple!("1".to_string(), "hello".to_string(), "3.14".to_string())
);

If the mapping function output the same type, the output type can be omitted:

use tuplez::*;

fn just<T: Copy>(v: &T) -> T { *v }

let tup = tuple!(1, "hello", 3.14);
let tup2 = tup.foreach(mapper! {
    _ : just where Copy
});
assert_eq!(tup, tup2);

§Use both

You can use both multiple specialized mapping rules and one universal mapping rule in a mapper! macro, but there are some restrictions.

  1. The universal mapping rule must be placed after all specialized mapping rules.
  2. Types that use specialized mapping rules must be exclusive from the bounds of the universal mapping rule.
  3. Either all types that use specialized mapping rules is your custom types, or the bounds of the universal mapping rule contain your custom traits.

Example of custom type:

use tuplez::*;

struct MyElement(i32);

fn to_string<T: ToString>(v: &T) -> String {
    v.to_string()
}

let tup = tuple!(MyElement(12), "hello", 3.14);
let tup2 = tup.foreach(mapper! {
    x : MyElement => i32 : x.0;
    _ => String: to_string where ToString
});
assert_eq!(tup2, tuple!(12, "hello".to_string(), "3.14".to_string()));

Example of custom trait:

use tuplez::*;

trait MyToString: ToString {}
impl MyToString for &str {}
impl MyToString for f32 {}

fn to_string<T: MyToString>(v: &T) -> String {
    v.to_string()
}

let tup = tuple!(vec![12, 14], "hello", 3.14);
/* Using `ToString` here is not allowed because
 * neither `Vec` nor `ToString` is defined in current crate,
 * even though `Vec` does not implement `ToString`.
 */
let tup2 = tup.foreach(mapper! {
    x : Vec<i32> => i32 : x[0];
    _ => String: to_string where MyToString
});
assert_eq!(tup2, tuple!(12, "hello".to_string(), "3.14".to_string()));