简体   繁体   English

枚举通用结构

[英]Enumerating generic structs

I wanted to try to build a proper implementation of Peano numbers using struct s, but it seems my generics game is not good enough yet and I could use some help. 我想尝试使用struct来构建Peano数字的正确实现,但似乎我的泛型游戏还不够好,我可以使用一些帮助。 I read the docs on generics and some StackOverflow questions , but they don't fit my case. 我阅读了有关泛型和一些 StackOverflow 问题的文档,但它们与我的情况不符。

I introduced a Peano trait and Zero and Succ types: 我介绍了Peano特性以及ZeroSucc类型:

trait Peano {}

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

And implemented a Peano trait for both types to be able to abstract over both: 并为这两种类型实现了Peano特性,以便能够对这两种类型进行抽象:

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

At first I wanted to implement std::ops::Add for Peano , but I quickly saw that I was doing something very wrong, so I decided to start with something simpler - enumeration: 最初,我想为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) }
}

What am I missing? 我想念什么? I experimented with boxing the results (though I would want to avoid that if possible), but the error just changed to mismatched types: Box<Succ<T>> instead of Box<Peano> , so I'm not sure this is helpful. 我尝试对结果进行装箱(尽管可能的话,我想避免这种情况),但是错误只是更改为mismatched types: Box<Succ<T>> instead of Box<Peano> ,所以我不确定这是否有帮助。

Full code below: 完整代码如下:

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) }
}

You have a T in Enumerate ... which serves no purpose. 您在Enumerate有一个T ,这毫无用处。

If you look back at your Peano trait, you will see that it has no T : the implementation for Succ has a parameter, but the trait itself does not. 如果回头看一下Peano特性,您会发现它没有TSucc的实现有一个参数,但特性本身没有。

The same applies here. 此处同样适用。

Let us start with a reduced scope: an Enumerate that can only go forward. 让我们从缩小的范围开始:一个只能前进的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) }
}

A few points of interest: 一些兴趣点:

  • you can refer to the current type as Self , very useful when defining a trait since the type of implementers is unknown in advance 您可以将当前类型称为Self ,在定义特征时非常有用,因为实现者的类型事先未知
  • you can constrain the implementers of a trait by using the : Peano + Sized syntax after the trait name 您可以通过在特征名称后使用: Peano + Sized语法来约束特征的实现者

Now, you also had a prev method which I did not implement. 现在,您还有一个我未实现的prev方法。 The thing is, it is nonsensical to apply prev to Zero . 事实是,将prev应用于Zero In this case, I propose that you rename Enumerate to Next and I'll show how to create a Prev trait: 在这种情况下,我建议您将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 }
}

The syntax type Output: Peano + Sized is an associated type , it allows each implementer to specify which type to use for their specific case (and avoid having the user of the trait, having to guess which type they should use). 语法type Output: Peano + Sized是一个关联类型 ,它允许每个实现者指定针对其特定情况使用的类型(并避免让trait的用户使用,而不得不猜测他们应该使用哪种类型)。

Once specified, it can be referred to as Self::Output within the trait or as <X as Prev>::Output from outside (if X implements Prev ). 指定后,可以将其称为特征中的Self::Output或外部的<X as Prev>::Output (如果X实现了Prev )。

And since the trait is separate, you only have a Prev implementation for Peano numbers that actually have a predecessor. 而且由于特征是独立的,因此您仅对实际上具有前身的Peano数字具有Prev实现。


Why the Sized constraint? 为什么选择Sized约束?

At the moment, Rust requires that return types have a known size. 目前,Rust要求返回类型的大小已知。 This is an implementation limit: in practice the caller has to reserve enough space on the stack for the callee to write down the return value. 这是实现上的限制:实际上,调用者必须在堆栈上保留足够的空间,以便被调用者记下返回值。

However... for type-level computation this is useless! 但是...对于类型级别的计算,这是没有用的! So, what do we do? 那么我们该怎么办?

Well, first we add convenient method of checking the result of our computations (prettier than the Debug output): 好吧,首先,我们添加了一种方便的方法来检查计算结果(比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());
}

Then... let's get rid of those methods, they bring nothing; 然后...让我们摆脱这些方法,它们什么也没带来; the reworked traits are thus: 因此,重做的特征是:

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());
}

and: 和:

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());
}

Now, you can go ahead and implement Add and co, though if you implement traits with methods you might need additional constraints. 现在,您可以继续实现Add和co,但是如果使用方法实现特征,则可能需要其他约束。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM