首页 > 解决方案 > 无需包装即可轻松实现此“Magma”特征

问题描述

我的目标是创建一个将类型元素转换为类型的Couple<Couple<Couple<T>>>函数T

大多数时候,接缝真的很容易做到。事实上,如果你有一个将 2 组合T成 1的运算符或函数T,你可以递归地进行。

If I want to fusion ((1 2) (3 4)) into a single number, 
I can use the "+" operator recursively:
(1 + 2) + (3 + 4) = 10

因此,我创建了一个名为Magma的特征(一种具有组合操作的类型),以及一个递归融合具有该特征的一对的函数。

// simple couple type
struct Couple<T>(T, T);

trait Magma {
    // a magma is just a type with a function   (S, S) -> S (for ex. the "+" operation)
    fn combine(a: Self, b: Self) -> Self;
}

// fn fusion<T>(Couple<Couple<Couple<T>>>) -> T where T: Magma {}

但问题是,要将此fusion函数与例如类型一起使用Couple<Couple<bool>>,我必须实现Magmabool

impl Magma for bool {
    fn combine(a: bool, b: bool) -> bool {
        a && b
    }
}

但是它们有很多方法可以组合 2 个布尔值来得到 1:“or”、“and”、“xor”......

而且我无法实现Magma这些功能中的每一个!

所以我目前的方法是在布尔值周围使用包装器:

struct OrWrapper(bool);

impl Magma for OrWrapper {
    fn combine(a: Self, b:Self) -> Self {
        OrWrapper(a.0 || b.0)
    }
}


struct AndWrapper(bool);

impl Magma for AndWrapper{
    fn combine(a: Self, b:Self) -> Self {
        AndWrapper(a.0 && b.0)
    }
}

但是写起来真的很重复和痛苦,我想知道是否有更优雅的解决方案。

任何想法 ?

标签: typesrustwrappertraits

解决方案


包装器类型几乎可以肯定是要走的路。岩浆被定义为一对:一个集合(即类型)和一个运算符,您必须以某种方式捕获它们。

您可以更改您的Magmatrait 以使其更方便使用,因此它可以接受内部类型并在内部转换为包装器:

trait Magma: Sized {
    fn combine(self, b: impl Into<Self>) -> Self;
}

如果您担心重复定义这些包装器类型,那么您可以使用宏来生成它们:

macro_rules! magma {
    ($($ty: ty as $wrapper: ident => $op: path),* $(,)?) => {
        $(
            // a new wrapper type
            #[derive(Copy, Clone, Debug)]
            pub struct $wrapper($ty);
            
            impl Magma for $wrapper {
                fn combine(self, b: impl Into<Self>) -> $wrapper {
                    $wrapper($op(&self.0, &b.into().0))
                }
            }
        )*
    }
}

magma! {
    bool as BoolAnd => std::ops::BitAnd::bitand,
    bool as BoolOr => std::ops::BitOr::bitor,
    u32 as U32Add => std::ops::Add::add,
    u32 as U32Mul => std::ops::Mul::mul,
}

为了进一步方便,您可能还希望实现From转换,Deref以及可能的其他特征,例如Display这些类型:

macro_rules! magma {
    ($($ty: ty as $wrapper: ident => $op: path),* $(,)?) => {
        $(
            // a new wrapper type
            #[derive(Copy, Clone, Debug)]
            pub struct $wrapper($ty);
            
            // conversion from the raw type to the wrapper
            impl From<$ty> for $wrapper {
                fn from(x: $ty) -> $wrapper { $wrapper(x) }
            }
            
            // conversion from the wrapper type to the inner type
            impl From<$wrapper> for $ty {
                fn from(w: $wrapper) -> $ty { w.0 }
            }
            
            // Deref to the inner type for convenience
            impl std::ops::Deref for $wrapper {
                type Target = $ty;
                fn deref(&self) -> &$ty { &self.0 }
            }
            
            // Delegate to the inner type for display
            impl std::fmt::Display for $wrapper {
                fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                    self.0.fmt(f)
                }
            }
            
            impl Magma for $wrapper {
                fn combine(self, b: impl Into<Self>) -> $wrapper {
                    $wrapper($op(&self.0, &b.into().0))
                }
            }
        )*
    }
}

用法:

magma! {
    bool as BoolAnd => std::ops::BitAnd::bitand,
    bool as BoolOr => std::ops::BitOr::bitor,
    u32 as U32Add => std::ops::Add::add,
    u32 as U32Mul => std::ops::Mul::mul,
}

fn main() {
    println!("{}", BoolOr(true).combine(false)); // true
}

推荐阅读