跨枚举变体的通用实现
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
我有一个枚举,其变体具有相同的结构(这些变体可以转换角色)并且出现在大型数据结构中。
我需要通过交换变体的角色来进行某些计算(相当长的程序)。 由于数据量大,我应该避免直接对数据进行这种排列。
为了尽量减少代码的长度并使未来的发展更加可靠,我决定以通用的方式编程。
到目前为止,我已经尝试过一些宏。 以下代码是我跨变体进行的通用实现的示例:(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