#generator #python #comprehension #list #comp

py-comp

实现类似 Python 生成器表达式的宏

4 个版本

0.1.3 2019 年 5 月 11 日
0.1.2 2019 年 4 月 21 日
0.1.1 2019 年 4 月 20 日
0.1.0 2019 年 4 月 20 日

#1339 in Rust 模式

MIT 许可证

18KB
191

py-comp - 一个实现类似 Python 生成器表达式的 Rust 宏

Latest Version Documentation License

此宏实现了一种语法,模拟 Python 的 生成器表达式 语法,并使其更符合 Rust 的常规语法。

这意味着 Python 语法与该宏提供的语法之间有一些细微的差异

  • forin 符号之间的模式是一个完整的 Rust 模式,可以是简单的符号,也可以是结构体解构等复杂形式。
  • in 符号之后定义迭代器的表达式必须评估为 Iteratorimpl IntoIterator
  • if 符号之后的条件表达式必须评估为布尔值。
  • 您可以在允许使用 if 子句的地方使用 if let 子句代替常规的 if 子句。在 if let 子句中引入的任何名称都可在任何后续子句中使用。
  • 生成器表达式开始的表达式、in 符号之后的表达式以及 if 符号之后的表达式都必须以分号 (;) 结尾。唯一的例外是宏中 inif 之后的最后一个表达式,可以省略尾随的分号。

comp!() 宏调用替换的表达式是一个惰性迭代器,其生命周期受其需要捕获的任何引用的限制。这意味着它可以被 .collect() 到您喜欢的任何容器中。

请注意,至少目前,在 in 子句中命名的所有对象(除了第一个 in 子句)必须是 Copy 类型,或者是由前面的 forif let 子句引入的。这是因为宏使用了一个 move 闭包(FnOnce)来处理每一层嵌套,这可能会导致捕获的对象需要被实例化多次,而不进行隐式克隆。同样,在 "yield" 表达式中命名的对象(位于第一个 for 子句之前)如果它们不是由最后的 forif let 子句引入的,也必须是 Copy 类型。这是因为它们可能被用于多个输出项。

将来可能会添加指定应该克隆哪些对象及其位置的功能,但这可能需要引入破坏性更改。

这是此宏使用的语法的 BNF 描述。

comprehension ::=  expression ";" comp_for [comp_iter] [";"]
comp_iter     ::=  ";" (comp_for | comp_if | comp_if_let)
comp_for      ::=  "for" pattern "in" expression [comp_iter]
comp_if       ::=  "if" expression [comp_iter]
comp_if_let   ::=  "if" "let" pattern ("|" pattern)* "=" expression [comp_iter]

就像在 Python 中一样,你可以嵌套任意多的 forifif let 子句。

示例

带有条件的简单生成器表达式

use py_comp::comp;

#[derive(Debug, PartialEq, Eq)]
struct Foo(i32);

let arr = &[Foo(11), Foo(12)];

// Notice the semicolons
let comp_vector = comp!(item; for item in arr; if item.0 % 10 == 2)
    .collect::<Vec<&Foo>>();

assert_eq!(comp_vector, vec![&Foo(12)])

带有条件和模式的笛卡尔积的三重产品

use py_comp::comp;

#[derive(Debug, PartialEq, Eq)]
struct Foo(i32);

// These need to be references to arrays because of how the closures
// that the macro expands to capture their environment.
let x = &[(Foo(11), "foo"), (Foo(12), "bar")];
let y = &[Foo(21), Foo(22)];
let z = &[Foo(31), Foo(32)];

let xyz = comp!(
    (a, b, c);
    for (a, _text) in x;  // You can use any function parameter pattern.
    if a.0 % 10 == 2;
    for b in y;           // Obviously not every level requires a conditional.
    for c in z;
    if c.0 % 10 == 2;
)
.collect::<Vec<(&Foo, &Foo, &Foo)>>();

// The result vector here is short for illustration purposes
// but can be as long as long as you need it to be.
assert_eq!(xyz, vec![(&Foo(12), &Foo(21), &Foo(32)), (&Foo(12), &Foo(22), &Foo(32))])

展开三重嵌套结构 + 复杂表达式

use py_comp::comp;

#[derive(Debug, PartialEq, Eq)]
struct Foo(i32);

let nested_3 = &[
    [
        [Foo(0), Foo(1), Foo(2)],
        [Foo(3), Foo(4), Foo(5)],
        [Foo(6), Foo(7), Foo(8)],
    ],
    [
        [Foo(9), Foo(10), Foo(11)],
        [Foo(12), Foo(13), Foo(14)],
        [Foo(15), Foo(16), Foo(17)],
    ],
    [
        [Foo(18), Foo(19), Foo(20)],
        [Foo(21), Foo(22), Foo(23)],
        [Foo(24), Foo(25), Foo(26)],
    ],
];

let nested_objects = comp!(
    {
        let inner = nested.0;
        Foo(inner + 1)
    };
    for nested_2 in nested_3;
    for nested_1 in nested_2;
    for nested in nested_1;
)
.collect::<Vec<Foo>>();

let expected_values = (1..28).map(Foo).collect::<Vec<Foo>>();

assert_eq!(expected_values, nested_objects);

依赖项

~9KB