笛卡尔积匹配

时间:2017-03-13 15:54:08

标签: macros nested rust match repeat

我有两组不完整的类型(即结构名称,缺少通用参数和生命周期),我需要为每对可能的组合执行一些代码:

// 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 ... }
    // ...
}

第一组的结构(ABC)和第二组的结构(XY,{{ 1}},W)具有相互依赖的通用参数(例如,对于案例Z,将使用的实际类型为("a", "x")A<X>)。出于这个原因,我找不到使用泛型函数或类似函数的任何解决方案。

我相信这个问题很容易用宏来解决;类似的东西:

X< A<X>::Color >

但是我已经在工作了几个小时之后实施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 ) 失败了。我找不到任何简单的方法来重复嵌套;我相信唯一的解决方案是使用宏来将匹配案例列表转换为嵌套的值元组,然后对它们进行递归,但我发现这很难实现。

另一种选择可能是使用构建脚本生成那个大product_match的代码,但这个解决方案听起来很脏。

我错过了这个问题的简单解决方案吗?有没有简单的方法来实现match?我该如何实现我的逻辑?

2 个答案:

答案 0 :(得分:1)

我认为您使用宏实现笛卡尔积的想法是最好的。

我不太确定你想要match表达式是什么,所以我implemented重复一个函数调用。然而,宏观管道应该大致相同。希望你能从这里拿走它。

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"]);
}

答案 1 :(得分:0)

Here是宏cartesian_match,可以按以下方式使用:

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,
        },
    );
}

调用cartesian_match有点麻烦(请注意所有花括号),并且可能不支持普通match语句中支持的所有模式。

宏的定义如下:

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,)*)
    };
);

它需要可变数量的match个项目,并以嵌套的方式逐个展开。这样做是在不同的“内部阶段”(在宏参数列表中以@前缀的参数表示):

  • 阶段@p0获取一个match es的列表,并将它们转换为单个tt。本质上,它将match_1, match_2, match_3,转换为类似(match_1, (match_2, (match_3, (),)))的东西。 (这样做是为了防止“不一致的锁步迭代”。)
  • 阶段@p1解压缩@p0生成的内容并将其转换为嵌套的match语句。它使用与@p0相同的技巧来存储与当前嵌套深度匹配的元素。
  • 阶段@p2解开@p1生成的内容(即,它实际上将((((), v3), v2), v1,)转换为v1, v2, v3并将其传递到指定的回调中。