泛型的泛型

Generics of Generic types

我打算在用 C++ 编程后开始学习 Rust。我不确定如何创建一个将泛型类型作为其模板参数的函数(或任何其他泛型)。

我试过编译以下代码:

trait Monad {
    fn singleton<T>(t: T) -> Self<T>;
    fn compact<T>(mmt: Self<Self<T>>) -> Self<T>;
}

fn id<F>(fi: F<i8>) -> F<i8> {return fi;}

但是这会吐出一堆 type argument not allowed 错误。

在 C++20 中我会这样写:

template<typename<typename> M>
concept monad = requires(...){...};

template<typename<typename> F>
F<std::byte> id(F<std::byte> fi) {return fi;}

我如何在 Rust 中实现这个功能?

模板模板参数是高级类型的有限形式。

Rust 也有一种有限形式的高级类型,以“通用关联类型”的形式出现。这些都是每晚提供的。具体来说,Monad 示例可能如下所示:

#![feature(generic_associated_types)]

trait Monad {
    type Type<T>;
    fn singleton<T>(t: T) -> Self::Type<T>;
    fn compact<T>(mmt: Self::Type<Self::Type<T>>) -> Self::Type<T>;
}

struct Vector;

impl Monad for Vector {
    type Type<T> = Vec<T>;
    fn singleton<T>(t: T) -> Self::Type<T> {
        vec![t]
    }
    fn compact<T>(mmt: Self::Type<Self::Type<T>>) -> Self::Type<T> {
        mmt.into_iter().flatten().collect()
    }
}

playground

在特征中,Self 始终是具体类型而不是类型构造函数。因为只有具体类型才能满足特征。这就是 Self<T> 不被接受的原因。

为了与 C++ 进行比较和对比,在 Rust 中你不能引用 Vec 而没有它的类型参数作为类型构造函数。 std::vector 本身在 C++ 中是可命名的。因此,我们必须在 Rust 中使用 Vector 来替代它。此外,我们的 Rust 实现使用了不稳定的语言功能。

另一方面,您很难用 C++ 完成 monad 概念的编写。模板模板参数不能应用于任何类型。类型构造函数是偏向的,而偏向是隐式的。对 monad 的合理要求可能是它可以用任何 std::movable 类型实例化,并且函数是为 std::movable 类型参数定义的。这在 C++ 中是无法表达的。

所以,在 C++ 中,你可能会这样写:

struct movable {
    movable() = delete;
    movable(const movable&) = delete;
    movable(movable&&) noexcept = default;
    movable& operator=(const movable&) = delete;
    movable& operator=(movable&&) noexcept = default;
    ~movable() = default;
};

template<template<typename> typename M>
struct type {};

template<template<typename> typename M>
concept monad = requires {
    { singleton(type<M>{}, movable{}) } -> std::same_as<M<movable>>;
    // compact is similar
};

在 C++ 中,您不能说约束适用于特定形状的所有类型的实例化(例如 std::movable)。因此,您创建了一个具有该形状的类型,仅此而已,并且要求约束保持在 just 那个实例化。人们习惯称这些类型为“原型”。您希望没有特化、重载或其他可能阻止这种满足感泛化到该形状的所有类型的东西。

然后,您可以选择函数所在的位置。模板模板参数不能有成员函数,所以你要么把它们放在一个特定的实例化中(例如 M<T> 可以从 T 构造)或者作为一个自由函数。使用成员函数有缺点,因为它不能在外部实现。使用自由函数,您需要一种识别 monad 的方法,因此您最终也将模板模板参数包装在替代类型中。

比较这些很有趣。

这些称为高级类型或类型构造函数。

Rust 目前没有这些,对该声明有两个警告。

  1. 很快就会有泛型关联类型之类的东西,它们很相似,但又不完全相同:

    trait Foo {
        type OutputCollection<T>;
        fn get_collection<T>(&self) -> Self::OutputCollection<T>;
    }
    

    阅读更多here and here

  2. 您可以使用辅助特征来模拟它们,但它看起来并不好看:

    trait ContainerHelper<Container> {
        type Container;
    }
    
    struct VecContainer;
    
    impl<T> ContainerHelper<VecContainer> for T {
        type Container = Vec<T>;
    }
    
    trait Foo {
        fn get_collection<T, Container>(&self) -> 
            <T as ContainerHelper<Container>>::Container
        where
            T: ContainerHelper<Container>;
    }