我想尝试使用struct
来构建一个正确的Peano数字实现,但似乎我的泛型游戏还不够好,我可以使用一些帮助。我阅读了关于泛型的文档和some StackOverflow questions,但它们不适合我的情况。
我介绍了Peano
特质以及Zero
和Succ
类型:
trait Peano {}
struct Zero;
struct Succ<T: Peano>(T);
并为两种类型实现了Peano
特征,以便能够对两者进行抽象:
impl Peano for Zero {}
impl<T> Peano for Succ<T> where T: Peano {}
起初我想为std::ops::Add
实现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) } // 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<Succ<T>> instead of Box<Peano>
,所以我不确定这是否有用。
以下完整代码:
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) }
}
答案 0 :(得分:4)
T
中有一个Enumerate
...没有任何意义。
如果你回顾一下Peano
特征,你会发现它没有T
:Succ
的实现有一个参数,但特征本身没有。
这同样适用于此。
让我们从缩小的范围开始: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
是关联类型,它允许每个实现者指定针对其特定情况使用的类型(并避免使用 user 特性,不得不猜测他们应该使用哪种类型。)
指定后,可以在特征中将其称为Self::Output
,或从外部称为<X as Prev>::Output
(如果X
实施Prev
)。
由于特征是独立的,因此您只有Prev
个Peano
实现1>}实际上拥有前任的数字。
为什么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,但如果使用方法实现特征,则可能需要其他约束。