在Rust中引用impl块中的特征
我正在努力将 Haskell 中的一个简单的 prolog 实现翻译成 Rust 以获得乐趣,并获得更多使用该语言的经验。
在 Haskell 中,我有一个类型类:
class Unifiable t v | t -> v where
variables :: t -> [v]
subs :: Unifier v t -> t -> t
unify :: t -> t -> (Maybe (Unifier v t))
我在 Rust 中将其转化为以下特征:
pub type Unifier<V,T> = HashMap<V,T>;
pub trait Unifiable<T,V> {
fn variables(term: T) -> Vec<V>;
fn subs(unifier: Unifier<V,T>, term: T) -> T;
fn unify(firstTerm: T, secondTerm: T) -> Option<Unifier<V,T>>;
然后,我定义了一个实用程序函数,只要有 的实例Unifiable可用,我就可以使用它。作为初步定义,我使用了这个:
pub fn compose<V: Hash + Eq, T, U: Unifiable<T,V>>(first: Unifier<V,T>, other: Unifier<V,T>) -> Unifier<V,T> {
let unifier: Unifier<V,T> = first.iter().map(|(&x,y)| (x, U::subs(other, *y))).collect();
unifier.extend(other);
return unifier;
}
我打算类似于 Haskell 类型签名:
compose :: Unifiable v t => Unifier v t -> Unifier v t -> Unifier v t
问题是,我想compose在implfor的块中使用这个辅助函数Unifiable,但我不确定如何引用Unifiable调用站点上的实例:
impl <T: Eq, V: Clone + Eq + Hash> Unifiable<Term<T,V>,V> for Term<T,V> {
...
fn unify(firstTerm: Term<T,V>, secondTerm: Term<T,V>) -> Option<Unifier<V,Term<T,V>>> {
....
return Some(compose<V,Term<T,V>,X>(u, us));
....
}
...
}
问题是,我不知道用什么来X引用我当前定义的 impl 块中的 Unifiable 实例,如果我省略了类型参数,我会收到“无法推断类型参数”错误。在 Rust 中可以使用这种带有特征的引用吗?
回答
以下是 Haskell 和 Rust 之间的差异,这些差异对于正确翻译此代码很重要:
- Haskell 的类型类以未区分的类型参数集合开始,但在 Rust 中,特征除了任何泛型参数之外还有一个“特殊”参数:特征实现的类型。在这种情况下,术语类型很自然
T就是那种类型。 - 相关地,Haskell 的“函数依赖”
t -> v在 Rust 中使用关联类型而不是类型参数来表达,这在 Rust 中与在 Haskell 中帮助类型推断同等重要。
总之,这些意味着你的 trait 可以不用类型参数来编写:T变成Self,V变成声明为type V;和使用为Self::V。
pub trait Unifiable {
type V;
fn variables(term: Self) -> Vec<Self::V>;
fn subs(unifier: Unifier<Self::V,Self>, term: Self) -> Self;
fn unify(firstTerm: Self, secondTerm: Self) -> Option<Unifier<Self::V,Self>>;
}
此外,由于其中一个 trait 方法返回Self,我们需要Self: Sized对 trait进行绑定。
我已经修改了你的程序,直到它在 Rust Playground 中编译——希望这仍然符合你的意图(我没有根据我的统一算法知识检查细节)。
注:T: Clone绑定的compose产生是因为subs需要term: Self通过值。如果 的实现subs通常会在不破坏输入的情况下产生一个新值,那么参数类型应该是,term: &Self并且您可以避免需要T: Clone(和执行克隆)这种方式。您可能希望再次通过您的程序,并在每一点考虑参数是应该通过移动还是通过引用传递,但实现比特征结构更能提供信息,所以我不能给你详细那里的建议。
use std::hash::Hash;
use std::collections::HashMap;
pub type Unifier<V,T> = HashMap<V,T>;
pub trait Unifiable where Self: Sized {
type V;
fn variables(term: Self) -> Vec<Self::V>;
fn subs(unifier: Unifier<Self::V,Self>, term: Self) -> Self;
fn unify(firstTerm: Self, secondTerm: Self) -> Option<Unifier<Self::V,Self>>;
}
pub fn compose<V: Hash + Eq + Clone, T: Unifiable<V = V> + Clone>(
first: Unifier<V, T>,
other: Unifier<V, T>,
) -> Unifier<V, T> {
let mut unifier: Unifier<V, T> = first
.into_iter()
.map(|(x, y)| (x, T::subs(other.clone(), y)))
.collect();
unifier.extend(other);
unifier
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct Term<V: Sized> {
v: V,
}
impl <V: Clone + Eq + Hash> Unifiable for Term<V> {
type V = V;
fn variables(term: Self) -> Vec<V> {todo!();}
fn subs(unifier: Unifier<V,Self>, term: Self) -> Self {todo!();}
fn unify(firstTerm: Self, secondTerm: Self) -> Option<Unifier<V,Self>> {
return Some(compose::<V,Self>(todo!(), todo!()));
}
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=73f3957a502e33d46092945ca564b588
(我没有编辑此代码的格式和样式,但请注意,使用lower_case_with_underscores名称而不是camelCase名称是惯用的。)