Cartesian product match

夙愿已清 提交于 2019-12-23 20:40:52

问题


I have two sets of incomplete types (i.e. struct names, missing generic parameters and lifetimes), and I need to have some code executed for each possible pair of combinations:

// these are my types
struct A<T> { ... }
struct B<'a, 'b, T> { ... }
struct C { ... }

struct X<T> { ... }
struct Y { ... }
struct W<'a> { ... }
struct Z<T, D> { ... }

// this is the code I need to generate
match (first_key, second_key) {
    ("a", "x") => { ... A ... X ... }
    ("a", "y") => { ... A ... Y ... }
    ("a", "w") => { ... A ... W ... }
    ("a", "z") => { ... A ... Z ... }
    ("b", "x") => { ... B ... X ... }
    ("b", "y") => { ... B ... Y ... }
    // ...
}

The structures of the first set (A, B, C) and the ones on the second set (X, Y, W, Z) have a generic parameter depending on each other (e.g. for the case ("a", "x"), the actual types that will be used are A<X> and X< A<X>::Color > ). For this reason I couldn't find any solution using generic functions or similar.

I believe that the problem could be easily solvable with a macro; something like:

macro_rules! my_code {
    ( $first_type:tt), $second_type:tt ) => {
        // ... $first_type ... $second_type ...
    }
}

product_match!( (first_key, second_key) {
    { "a" => A, "b" => B, "c" => C },
    { "x" => X, "y" => Y, "w" => W, "z" => Z }
} => my_code )

but I failed at implementing product_match after working for several hours on it already. I couldn't find any easy way to nest repetitions; I believe that the only solution is using macros to turn the lists of match cases into nested tuples of values, and then recurse over them, but I found this very difficult to implement.

Another option could be generating the code of that big match using a build script, but this solution sounds quite dirty.

Is there any easy solution to this problem that I missed? Is there any easy way to implement product_match!? How can I implement my logic?


回答1:


I think your idea of implementing a Cartesian product using macros is best.

I'm not quite sure what you want the match expression to be, so I've implemented a repeated function call instead. The macro plumbing should be much the same, however. Hopefully you can take it from here.

macro_rules! cp_inner {
    ($f: expr, $x: expr, [$($y: expr),*]) => {
        $($f($x, $y);)*
    }
}

macro_rules! cartesian_product {
    ($f: expr, [$($x: expr),*], $ys: tt) => {
        $(cp_inner!($f, $x, $ys);)*;
    }
}

fn print_pair(x: u32, y: &'static str) {
    println!("({}, {})", x, y);
}

pub fn main() {
    cartesian_product!(print_pair, [1, 2, 3], ["apple", "banana", "cherry"]);
}



回答2:


Here is a macro cartesian_match that can be used as follows:

fn main() {
    macro_rules! test( ($x: tt, $y: tt, $z: tt,) => {
        println!("{:?} {:?} {:?}", $x, $y, $z);
    });
    #[derive(Debug)]
    enum E {
        V1, V2, V3,
    }
    let b = false;
    let oi = Some(6);
    let e = E::V1;
    cartesian_match!(
        test,
        match (oi) {
            Some(n) => {format!("{} is the number", n)},
            None => {None as Option<usize>},
        },
        match (b) {
            true => true,
            false => {E::V3},
        },
        match (e) {
            E::V1 => true,
            E::V2 => 'l',
            E::V3 => 2,
        },
    );
}

Calling cartesian_match is a bit rough around the edges (note all the braces), and possibly does not support all the patterns supported in ordinary match statements.

The macro is defined as follows:

macro_rules! cartesian_match(
    (
        $macro_callback: ident,
        $(match ($e: expr) {
            $($x: pat => $y: tt,)*
        },)*
    ) => {
        cartesian_match!(@p0,
            $macro_callback,
            (),
            $(match ($e) {
                $($x => $y,)*
            },)*
        )
    };
    (@p0,
        $macro_callback: ident,
        $rest_packed: tt,
        match ($e: expr) {
            $($x: pat => $y: tt,)*
        },
        $(match ($e2: expr) {
            $($x2: pat => $y2: tt,)*
        },)*
    ) => {
        cartesian_match!(@p0,
            $macro_callback,
            (
                match ($e) {
                    $($x => $y,)*
                },
                $rest_packed,
            ),
            $(match ($e2) {
                $($x2 => $y2,)*
            },)*
        )
    };
    (@p0,
        $macro_callback: ident,
        $rest_packed: tt,
    ) => {
        cartesian_match!(@p1,
            $macro_callback,
            @matched{()},
            $rest_packed,
        )
    };
    (@p1,
        $macro_callback: ident,
        @matched{$matched_packed: tt},
        (
            match ($e: expr) {
                $($x: pat => $y: tt,)*
            },
            $rest_packed: tt,
        ),
    ) => {
        match $e {
            $($x => cartesian_match!(@p1,
                $macro_callback,
                @matched{ ($matched_packed, $y,) },
                $rest_packed,
            ),)*
        }
    };
    (@p1,
        $macro_callback: ident,
        @matched{$matched_packed: tt},
        (),
    ) => {
        cartesian_match!(@p2,
            $macro_callback,
            @unpacked(),
            $matched_packed,
        )
        //$macro_callback!($matched_packed)
    };
    (@p2,
        $macro_callback: ident,
        @unpacked($($u: tt,)*),
        (
            $rest_packed: tt,
            $y: tt,
        ),
    ) => {
        cartesian_match!(@p2,
            $macro_callback,
            @unpacked($($u,)* $y,),
            $rest_packed,
        )
    };
    (@p2,
        $macro_callback: ident,
        @unpacked($($u: tt,)*),
        (),
    ) => {
        $macro_callback!($($u,)*)
    };
);

It takes a variable number of match items and expands them in a nested manner one after another. It does so in different "internal phases" (denoted by the @-prefixed parameter in the macro argument lists):

  • Phase @p0 takes a list of matches and converts them into a single tt. Essentially, it transforms match_1, match_2, match_3, into something like (match_1, (match_2, (match_3, (),))). (This is done to prevent "inconsistent lockstep iteration".)
  • Phase @p1 unpacks the thing generated by @p0 and converts it into nested match statements. It uses the same trick as @p0 to store the elements matched up to the current nesting depth.
  • Phase @p2 unpacks the thing generated by @p1 (i.e. it essentially converts ((((), v3), v2), v1,) to v1, v2, v3 and passes it into the specified callback.


来源:https://stackoverflow.com/questions/42767999/cartesian-product-match

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!