【问题标题】:Enumerating generic structs枚举泛型结构
【发布时间】:2016-09-16 01:33:12
【问题描述】:

我想尝试使用structs 构建 Peano 数字的正确实现,但我的泛型游戏似乎还不够好,我可以使用一些帮助。我阅读了有关泛型和 some StackOverflow questions 的文档,但它们不适合我的情况。

我引入了Peano 特征和ZeroSucc 类型:

trait Peano {}

struct Zero;
struct Succ<T: Peano>(T);

并为这两种类型实现了Peano trait,以便能够对两者进行抽象:

impl Peano for Zero {}
impl<T> Peano for Succ<T> where T: Peano {}

一开始我想为Peano实现std::ops::Add,但很快我就发现我做的很不对,所以我决定从更简单的东西开始——枚举:

trait Enumerate<T: Peano> {
    fn succ(&self) -> Succ<T>;
    fn pred(&self) -> Option<T>;
}

impl<T> Enumerate<T> for Zero where T: Peano {
    fn succ(&self) -> Succ<T> { Succ(*self) } // mismatched types: Zero instead of T
    fn pred(&self) -> Option<T> { None }
}

impl<T> Enumerate<T> for Succ<T> where T: Peano {
    fn succ(&self) -> Succ<T> { Succ(*self) } // mismatched types: Succ<T> instead of T
    fn pred(&self) -> Option<T> { Some(self.0) }
}

我错过了什么?我尝试将结果装箱(尽管我希望尽可能避免这种情况),但错误刚刚更改为mismatched types: Box&lt;Succ&lt;T&gt;&gt; instead of Box&lt;Peano&gt;,所以我不确定这是否有用。

完整代码如下:

trait Peano {}

#[derive(Debug, Clone, Copy, PartialEq)]
struct Zero;

#[derive(Debug, Clone, Copy, PartialEq)]
struct Succ<T: Peano>(T);

impl Peano for Zero {}
impl<T> Peano for Succ<T> where T: Peano {}

trait Enumerate<T: Peano> {
    fn succ(&self) -> Succ<T>;
    fn pred(&self) -> Option<T>;
}

impl<T> Enumerate<T> for Zero where T: Peano {
    fn succ(&self) -> Succ<T> { Succ(*self) }
    fn pred(&self) -> Option<T> { None }
}

impl<T> Enumerate<T> for Succ<T> where T: Peano {
    fn succ(&self) -> Succ<T> { Succ(*self) }
    fn pred(&self) -> Option<T> { Some(self.0) }
}

【问题讨论】:

    标签: generics struct rust


    【解决方案1】:

    您在Enumerate 中有一个T... 没有任何用处。

    如果您回头查看您的 Peano 特征,您会发现它没有 TSucc 的实现有一个参数,但特征本身没有。

    这里也一样。

    让我们从一个缩小的范围开始:一个只能前进的Enumerate

    use std::marker::Sized;
    
    trait Peano {}
    
    #[derive(Debug, Clone, Copy, PartialEq)]
    struct Zero;
    
    #[derive(Debug, Clone, Copy, PartialEq)]
    struct Succ<T: Peano>(T);
    
    impl Peano for Zero {}
    impl<T> Peano for Succ<T> where T: Peano {}
    
    trait Enumerate: Peano + Sized {
        fn succ(self) -> Succ<Self>;
    }
    
    impl Enumerate for Zero {
        fn succ(self) -> Succ<Self> { Succ(self) }
    }
    
    impl<T> Enumerate for Succ<T> where T: Peano {
        fn succ(self) -> Succ<Succ<T>> { Succ(self) }
    }
    

    几个兴趣点:

    • 您可以将当前类型称为Self,这在定义特征时非常有用,因为事先不知道实现者的类型
    • 您可以通过在特征名称后使用 : Peano + Sized 语法来约束特征的实现者

    现在,您还有一个我没有实现的 prev 方法。问题是,将prev 应用于Zero 是荒谬的。在这种情况下,我建议您将 Enumerate 重命名为 Next,然后我将展示如何创建 Prev 特征:

    trait Prev: Peano + Sized {
        type Output: Peano + Sized;
        fn prev(self) -> Self::Output;
    }
    
    impl<T> Prev for Succ<T> where T: Peano {
        type Output = T;
        fn prev(self) -> Self::Output { self.0 }
    }
    

    type Output: Peano + Sized 语法是一个关联类型,它允许每个实现者为他们的特定情况指定使用哪种类型(并避免使用 trait 的 user ,不得不猜测他们应该使用哪种类型)。

    一旦指定,它可以在 trait 内被称为 Self::Output 或从外部被称为 &lt;X as Prev&gt;::Output(如果 X 实现 Prev)。

    而且由于 trait 是独立的,所以您只有一个 Prev 实现,用于实际具有前身的 Peano 数字。


    为什么要使用Sized 约束?

    目前,Rust 要求返回类型具有已知大小。这是一个实现限制:实际上调用者必须在堆栈上保留足够的空间,以便被调用者写下返回值。

    但是...对于类型级计算,这是没用的!那么,我们该怎么办?

    好吧,首先我们添加检查计算结果的便捷方法(比Debug 输出更漂亮):

    trait Value: Peano {
        fn value() -> usize;
    }
    
    impl Value for Zero {
        fn value() -> usize { 0 }
    }
    
    impl<T> Value for Succ<T> where T: Value {
        fn value() -> usize { T::value() + 1 }
    }
    
    fn main() {
        println!("{}", Succ::<Zero>::value());
    }
    

    那么……让我们摆脱那些方法,它们什么也没带来;因此,重做的特征是:

    trait Next: Peano {
        type Next: Peano;
    }
    
    impl Next for Zero {
        type Next = Succ<Zero>;
    }
    
    impl<T> Next for Succ<T> where T: Peano {
        type Next = Succ<Succ<T>>;
    }
    
    fn main() {
        println!("{}", <Zero as Next>::Next::value());
    }
    

    和:

    trait Prev: Peano {
        type Prev: Peano;
    }
    
    impl<T> Prev for Succ<T> where T: Peano {
        type Prev = T;
    }
    
    fn main() {
        println!("{}", <<Zero as Next>::Next as Prev>::Prev::value());
    }
    

    现在,您可以继续实现 Add 和 co,但如果您使用方法实现特征,则可能需要额外的约束。

    【讨论】:

    • 感谢您提供有关类型级计算的其他信息 - 我的 trait-foo 已经改进了很多。
    • @ljedrz:我会注意到,如果你想“深入”到类型级计算,你可能会对typenum crate 感兴趣。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多