跨枚举变体的通用实现

Generic implementation across the variants of an enum

我有一个枚举,其变体具有相同的结构(这些变体可以转换角色)并且出现在大型数据结构中。

我需要通过交换变体的角色来进行某些计算(相当长的程序)。 由于数据量大,我应该避免直接对数据进行这种排列。

为了尽量减少代码的长度并使未来的发展更加可靠,我决定以通用的方式编程。

到目前为止,我已经尝试过一些宏。 以下代码是我跨变体进行的通用实现的示例:(playground)

// enum with same variants shapes
#[derive(Debug, Clone)]
enum Choice{ 
    A{ val: f32, }, 
    B{ val: f32, }, 
}
use Choice::{A,B,};

// code to be implemented
trait MyTrait<const P: usize> { // P is a permutation parameter
    fn eval(&self) -> f32;
    fn zoom(&mut self);
    fn transmute(self) -> Self;
}

// variant permutation macros
macro_rules! choice_a { 
    (0 | val: $a:ident) => (A{val: $a}); 
    (1 | val: $a:ident) => (B{val: $a});
    (0 | val: ref $a:ident) => (A{val: ref $a}); 
    (1 | val: ref $a:ident) => (B{val: ref $a});
    (0 | val: ref mut $a:ident) => (A{val: ref mut $a}); 
    (1 | val: ref mut $a:ident) => (B{val: ref mut $a});
    (0 | val: $a:expr) => (A{val: $a}); 
    (1 | val: $a:expr) => (B{val: $a});
}
macro_rules! choice_b { 
    (0 | val: $a:ident) => (B{ val: $a}); 
    (1 | val: $a:ident) => (A{ val: $a});
    (0 | val: ref $a:ident) => (B{ val: ref $a}); 
    (1 | val: ref $a:ident) => (A{ val: ref $a});
    (0 | val: ref mut $a:ident) => (B{ val: ref mut $a}); 
    (1 | val: ref mut $a:ident) => (A{ val: ref mut $a});
    (0 | val: $a:expr) => (B{ val: $a}); 
    (1 | val: $a:expr) => (A{ val: $a}); 
}

// macro for generic implementation
macro_rules! mytrait {
    ($p:tt) => (
        impl MyTrait<$p> for Choice {
            fn eval(&self) -> f32 {
                match *self {
                    choice_a!{$p | val: ref va} => { va.powi(2) },
                    choice_b!{$p | val: ref va} => { va.sqrt() },
                }
            }
            fn zoom(&mut self) {
                match *self {
                    choice_a!{$p | val: ref mut va} => { *va *= 2.0; },
                    choice_b!{$p | val: ref mut va} => { *va /= 2.0; },
                }
            }
            fn transmute(self) -> Self {
                match self {
                    choice_a!{$p | val: va} => { choice_b!{$p | val: va + 1.0} },
                    choice_b!{$p | val: va} => { choice_a!{$p | val: va - 1.0} },
                }
            }
        }
    );
}
mytrait!(0); // no permutation case
mytrait!(1); // permutation case

fn main() {
    
    for x in vec![Choice::A{ val: 16.0}, Choice::B{ val: 16.0}] {
        let mut y = x.clone();
        println!("Variants roles are not permuted:");
        println!("y = {:?}", y);
        println!("eval y = {:?}", MyTrait::<0>::eval(&y));
        MyTrait::<0>::zoom(&mut y);
        println!("zoomed y = {:?}", y);
        let y = MyTrait::<0>::transmute(y);
        println!("transmuted zoomed y = {:?}", y);
        println!("-------------------------");
        println!("Variants roles are permuted:");
        let mut z = x;
        println!("z = {:?}", z);
        println!("eval z = {:?}", MyTrait::<1>::eval(&z));
        MyTrait::<1>::zoom(&mut z);
        println!("zoomed z = {:?}", z);
        let z = MyTrait::<1>::transmute(z);
        println!("transmuted zoomed z = {:?}", z);
        println!("=====================================");
    }
    
}

结果是:

Variants roles are not permuted:
y = A { val: 16.0 }
eval y = 256.0
zoomed y = A { val: 32.0 }
transmuted zoomed y = B { val: 33.0 }
-------------------------
Variants roles are permuted:
z = A { val: 16.0 }
eval z = 4.0
zoomed z = A { val: 8.0 }
transmuted zoomed z = B { val: 7.0 }
=====================================
Variants roles are not permuted:
y = B { val: 16.0 }
eval y = 4.0
zoomed y = B { val: 8.0 }
transmuted zoomed y = A { val: 7.0 }
-------------------------
Variants roles are permuted:
z = B { val: 16.0 }
eval z = 256.0
zoomed z = B { val: 32.0 }
transmuted zoomed z = A { val: 33.0 }
=====================================

我的代码可以工作,但并不完全令人满意。 事实上,我的 choice_a!choice_b! 宏不是进化的,它们的使用是严格的,并且产生枚举的完整模式或完整定义。 如果这些宏只生成枚举的名称会更好。 例如,如果能够编写 choice_a!($p) { val: va - 1.0} 而不是 [=39= 会更好]!{$p | val: va - 1.0},最好能写成 choice_b!($p){ val: ref mut va}choice_b!{$p | val: ref mut va}.

这可以吗? 除了宏之外,还有其他方法可以跨变体执行这种通用实现吗?

不确定我是否在评论问题试图解决的问题(所以如果我不合时宜,请忽略这个答案)。但是,完全避免使用宏并尝试使用类似这样的方法不是更容易(或至少更具可读性)吗?


// enum with same variants shapes
#[derive(Debug, Clone)]
enum Choice{
    A{ val: f32, },
    B{ val: f32, },
}
use Choice::{A,B,};

// code to be implemented
trait MyTrait<const P: bool> { // P is a permutation parameter
fn eval(&self) -> f32;
    fn zoom(&mut self);
    fn transmute(self) -> Self;
}

impl Choice {

    fn as_val(&self) -> &f32 {
        match self {
            Choice::A { val } => val,
            Choice::B { val } => val,
        }
    }

    fn as_val_mut(&mut self) -> &mut f32 {
        match self {
            Choice::A { val } => val,
            Choice::B { val } => val,
        }
    }

    /// If P=false, primary choice is A, secondary B. If P=true, the values are switched.
    fn is_primary<const P: bool>(&self) -> bool {
        match self {
            Choice::A { .. } => !P,
            Choice::B { .. } => P,
        }
    }

    fn flip_type(self) -> Choice {
        match self {
            Choice::A { val } => Choice::B { val },
            Choice::B { val } => Choice::A { val }
        }
    }

}

impl <const P: bool> MyTrait<P> for Choice {
    fn eval(&self) -> f32 {
        if self.is_primary::<P>() {
            self.as_val().powi(2)
        } else {
            self.as_val().sqrt()
        }
    }

    fn zoom(&mut self) {
        if self.is_primary::<P>() {
            *self.as_val_mut() *= 2.0;
        } else {
            *self.as_val_mut() /= 2.0;
        }
    }

    fn transmute(self) -> Self {
        let is_primary = self.is_primary::<P>();
        let mut copy = self.flip_type();
        if is_primary {
            *copy.as_val_mut() += 1.0;
        } else {
            *copy.as_val_mut() -= 1.0;
        };
        copy
    }
}


fn main() {

    for x in vec![Choice::A{ val: 16.0}, Choice::B{ val: 16.0}] {
        let mut y = x.clone();
        println!("Variants roles are not permuted:");
        println!("y = {:?}", y);
        println!("eval y = {:?}", MyTrait::<false>::eval(&y));
        MyTrait::<false>::zoom(&mut y);
        println!("zoomed y = {:?}", y);
        let y = MyTrait::<false>::transmute(y);
        println!("transmuted zoomed y = {:?}", y);
        println!("-------------------------");
        println!("Variants roles are permuted:");
        let mut z = x;
        println!("z = {:?}", z);
        println!("eval z = {:?}", MyTrait::<true>::eval(&z));
        MyTrait::<true>::zoom(&mut z);
        println!("zoomed z = {:?}", z);
        let z = MyTrait::<true>::transmute(z);
        println!("transmuted zoomed z = {:?}", z);
        println!("=====================================");
    }

}

此代码为您提供相同的结果。 is_primary 检查应该编译成与原始匹配宏基本相同的东西,因此性能应该相似(但我可以检查你是否想确定)。它稍长一些,但总的来说我觉得它更具可读性,而且新方法在实现 MyTrait.

之外也很有用

用简单的宏改进回答:(playground)

// enum with same variants shapes
#[derive(Debug, Clone)]
enum Choice{ 
    A{ val: f32, }, 
    B{ val: f32, }, 
}
use Choice::{A,B,};

// variant permutation macros; space before * is required!
macro_rules! a { 
    (0 | $($t:tt) *) => (A{$($t) *}); 
    (1 | $($t:tt) *) => (B{$($t) *});
}
macro_rules! b { 
    (0 | $($t:tt) *) => (B{$($t) *}); 
    (1 | $($t:tt) *) => (A{$($t) *});
}

// code to be implemented
trait MyTrait<const P: usize> { // P is a permutation parameter
    fn eval(&self) -> f32;
    fn zoom(&mut self);
    fn transmute(self) -> Self;
}

// macro for generic implementation
macro_rules! mytrait {
    ($p:tt) => (
        impl MyTrait<$p> for Choice {
            fn eval(&self) -> f32 {
                match *self {
                    a!{$p | val: ref va} => { va.powi(2) },
                    b!{$p | val: ref va} => { va.sqrt() },
                }
            }
            fn zoom(&mut self) {
                match *self {
                    a!{$p | val: ref mut va} => { *va *= 2.0; },
                    b!{$p | val: ref mut va} => { *va /= 2.0; },
                }
            }
            fn transmute(self) -> Self {
                match self {
                    a!{$p | val: va} => { b!{$p | val: va + 1.0} },
                    b!{$p | val: va} => { a!{$p | val: va - 1.0} },
                }
            }
        }
    );
}
mytrait!(0); // no permutation case
mytrait!(1); // permutation case

上一个回答:

enum Choice{ A{ val: f32, }, B{ val: f32, }, } 替换为 type Choice = (Variant,Data) with enum Variant{ A, B, } and struct Data { val: f32 }: (playground)

// variant label
#[derive(Debug, Clone, Copy,)]
enum Variant{ A, B, }
use Variant::{A,B,};
// data fields
#[derive(Debug, Clone,)]
struct Data { val: f32 }
// enum like type
type Choice = (Variant,Data);


macro_rules! a { (0) => (A); (1) => (B); }
macro_rules! b { (0) => (B); (1) => (A); }

// code to be implemented
trait MyTrait<const P: usize> { // P is a permutation parameter
    fn eval(&self) -> f32;
    fn zoom(&mut self);
    fn transmute(self) -> Self;
}

// macro for generic implementation
macro_rules! mytrait {
    ($p:tt) => (
        impl MyTrait<$p> for Choice {
            fn eval(&self) -> f32 {
                match *self {
                    (a!($p), Data{val: ref v}) => { v.powi(2) },
                    (b!($p), Data{val: ref v}) => { v.sqrt() },
                }
            }
            fn zoom(&mut self) {
                match *self {
                    (a!($p), Data{val: ref mut v}) => { *v *= 2.0; },
                    (b!($p), Data{val: ref mut v}) => { *v /= 2.0; },
                }
            }
            fn transmute(self) -> Self {
                match self {
                    (a!($p), Data{val: v}) => (b![$p], Data{val: v + 1.0}),
                    (b!($p), Data{val: v}) => (a![$p], Data{val: v - 1.0}),
                }
            }
        }
    );
}
mytrait!(0); // no permutation case
mytrait!(1); // permutation case