什么是高阶类型啊?如何去定义高阶类型呢

电子说

1.3w人已加入

描述

GAT

Rust在类型系统级别上与Haskell,Scala有许多相似之处。

两者都有类型(type),泛型类型(generic types),关联类型(associated types)和特质/类型类(traits/type classes)(基本上是等效的)。

但是,Haskell有Rust缺乏的一个特性:高阶类型(Higher Kinded Types), 也就是通常说的HKTs。

这不是Rust故意不添加,也不是Rust应该填补的一些差距。其实这是Rust的一个有意的设计。

结果就是,到没有GATs出现时,某些代码无法真正在Rust中实现。

以Haskell中的Functor为例。Functor其实是对类型上实现的map的一个更加抽象的实现,不关心具体类型的一个接口。

比如,在Scala中,要实现一个map的Functor是这样的:

 

import scala.language.higherKinds

trait Functor[F[_]] {
    def map[A, B](fa: F[A])(f: A => B): F[B]
}

 

如果你使用过Scala的集合api,这看起来应该非常相似。所有集合都可以使用map。

 

List(1, 2, 3).map(_ + 1)
// List(2, 3, 4)

 

你可能要问,什么是高阶类型啊.你可以先理解为: 高阶类型是类型的类型。这是什么意思呢? 我以Scala里面的代码来说明一下。

 

scala> trait MyList[A] {
  def head: A
  def tail: MyList[A]
}

 

然后用:k命令看下是什么类型:

 

scala> :k -v MyList
MyList's kind is F[A]
* -> *
This is a type constructor: a 1st-order-kinded type.

 

MyList[String]类型是一个一阶类型(1st-order-kinded type);任何MyList的类型都是由A参数化的,可以将MyList看作是一个类型函数,如A => MyList[A],因此给定一个类型A,我们可以创建一个新的类型MyList[A]。如果MyList是一阶类型,那么什么是类型化类型呢?其实想想,什么是高阶函数啊? 它不就是一个接受函数然后返回另外另一个函数的函数。同理可得,什么是高阶类型呢? 它是由另一个类型参数化的类型。我再写一个简单的例子。

 

scala> trait Foo[F[_]]:k -v Foo
Foo's kind is X[F[A]]
(* -> *) -> *
This is a type constructor that takes type constructor(s): a higher-kinded type.

 

这里的Foo就是一个高阶类型。它是一个类型构造函数,参数也是一个类型构造函数。为了说清楚这点,我再写一个例子.我写一个trait,里面有一个leftFold的方法。

 

scala> trait Foldable[F[_]] {
     |     def leftFold[A,B](ob: F[A])(zero: B)(fn: (B,A) => B): B
     | }

 

然后呢,我再实现一个listFoldable, 这是最常见的吧。

 

scala> implicit val listFoldable: Foldable[List] = new Foldable[List] {
     |   def leftFold[A, B](ob: List[A])(zero: B)(fn: (B, A) => B): B = {
     |     ob.foldLeft(zero)(fn)
     |   }
     | }

 

上面我定义了一个适用于任何List类型的Foldable对象。leftFold方法将取A并使用A构造List[A]。

一种更好的写法:

 

scala> import scala.language.implicitConversions
import scala.language.implicitConversions

scala> implicit class ListFoldableOpt[A](list: List[A])(implicit fold: Foldable[List]) {
     |     def leftFold[B](zero: B)(fn: (B, A) => B): B =
     |         fold.leftFold(list)(zero)(fn)
     | }
     
scala> List(1, 2, 3).leftFold(0)(_ + _) // 6

 

这里先收一下,回到Rust中。

Rust中的很多不同结构都实现了map函数,比如: Option, Result, Iterator, and Future.

但是, 在Rust里面,还不能编写可以给多种类型实现的通用Functortrait, 就像我刚才上面写的trait Functor[F[_]]。通常呢 在Rust里面是单个类型单独去实现map。例如,我这里自己写了一个MyOption和MyResult枚举类, 并实现了map方法.

 

#[derive(Debug, PartialEq)]
    enum MyOption {
    Some(A),
    None,
}

impl MyOption {
    fn map B, B>(self, f: F) -> MyOption {
        match self {
            MyOption::Some(a) => MyOption::Some(f(a)),
            MyOption::None => MyOption::None,
        }
    }
}

#[test]
fn test_option_map() {
    assert_eq!(MyOption::Some(5).map(|x| x + 1), MyOption::Some(6));
    assert_eq!(MyOption::None.map(|x: i32| x + 1), MyOption::None);
}

#[derive(Debug, PartialEq)]
enum MyResult {
    Ok(A),
    Err(E),
}

impl MyResult {
    fn map B, B>(self, f: F) -> MyResult {
        match self {
            MyResult::Ok(a) => MyResult::Ok(f(a)),
            MyResult::Err(e) => MyResult::Err(e),
        }
    }
}

#[test]
fn test_result_map() {
    assert_eq!(MyResult::Ok(5).map(|x| x + 1), MyResult::(6));
    assert_eq!(MyResult::Err("hello").map(|x: i32| x + 1), MyResult::Err("hello"));
}

 

上面的几个例子都是直接在自己的结构中定义的map方法。如果没有GATs, 就不可能将map定义为一个trait的方法。这是为什么呢?下面是一个在Rust里面简单的Functortrait实现,以及Option的实现.

 

trait NaiveFunctor {
    type T;
    fn map(self, f: F) -> Self
        where
            F: FnMut(Self::T) -> Self::T;
}

impl NaiveFunctor for Option {
    type T = A;
    fn map A>(self, mut f: F) -> Option {
        match self {
            Some(a) => Some(f(a)),
            None => None,
        }
    }
}

 

在上面的trait定义中,先给NaiveFunctor内部的值定义了一个关联类型T。给Optionimpl这个trait,T=A。这就是问题所在。Rust将T硬编码为一种类型A,因为通常在map函数中,希望返回的类型是B,但在之前版本稳定的Rust中,没有办法说"我想要一个既能与NaiveFunctor关联的类型,又要求这个类型和关联类型有一点不一样。

这就是泛型关联类型发挥作用的地方了。

多态Functor的实现

为了得到一个多态Functor.我想要的是:"我的类型最终应该是我在其中包裹的类型".例如,对于Option,我想说的是"如果我有一个Option,那么它肯定包含一个A类型,但如果它包含一个B类型,它将是Option"

这里就需要使用泛型关联类型.

 

trait Functor {
    type Unwrapped;
    type Wrapped: Functor;
    fn map(self, f: F) -> Self::Wrapped
        where
            F: FnMut(Self::Unwrapped) -> B;
}

 

说明下上面的写法:

每个Functor都有一个关联的Unwrapped类型.

还有另一个关联类型Wrapped,它与Self类似,但有一个不同的包装值

和前面例子一样, map接受两个参数的一个是:self和一个函数

函数形参将从当前关联的Unwrapped值映射到一个新的类型B

map的输出是一个Wrapped

有点抽象。具体点现在看下Option类型是什么样子的

 

impl Functor for Option {
    type Unwrapped = A;
    type Wrapped = Option;

    fn map B, B>(self, mut f: F) -> Option where F: FnMut(A) -> B {
        match self {
            Some(x) => Some(f(x)),
            None => None,
        }
    }
}

#[test]
fn test_option_map() {
    assert_eq!(Some(5).map(|x| x + 1), Some(6));
    assert_eq!(None.map(|x: i32| x + 1), None);
}

 

编译通过,非常优雅。

类型类

在Haskell和Scala中,其实是不需要这种泛型关联类型的。事实上,Haskell中Functors不使用任何关联类型。Haskell中Functor的类型类远远早于其他语言中关联类型的出现。为了进行比较,先看看它是什么样子。

 

class Functor f where
    map :: (a -> b) -> f a -> f b
    
instance Functor Option where
    map f option =
        case option of
            Some x -> Some (f x)
            None -> None
trait Functor[F[_]] {
    def map[A, B](fa: F[A])(f: A => B): F[B]
}

 

把它转换成Rust就是如下:

 

trait HktFunctor {
    fn map B>(self: Self, f: F) -> Self;
}

impl HktFunctor for Option {
    fn map B>(self: Option, f: F) -> Option {
        match self {
            Some(a) => Some(f(a)),
            None => None,
        }
    }
}

 

但是上面的代码是编译不通过的。因为我试图给Self提供类型参数。但是在Rust中, 单独一个Option不是一个类型. Option要成为一个类型,必须有一个类型参数.比如: Option 是一个类型. Option却不是.

相反,在Haskell中,Maybe Int是kind type的一种类型。Maybe是类型构造函数,类型为type -> type。但是出于创建类型类和实例的目的,可以将Maybe处理为具有自己的类型。Haskell中的Functor作用于type->type。

这就是我所说的"高阶类型": 就是说我可以写出拥有类型的类型(Kind)。

Pointer 的实现

Haskell中有一个有争议的类型类叫做Pointed。之所以有争议的,是因为它引入了一个类型类,但没有与它相关的任何laws。我想在Rust中实现下Pointed。

什么是Pointed

Pointed的思想很简单:将一个值包装成一个类似Functor的东西。比如:在Option的情况下,它就用Some包装它。在Result中,它使用Ok。

对于Vec,它是一个单值向量。与Functor不同,这里是一个静态方法,因为我们没有现有的Point值要改。让我们看看它在Rust中的实现。

 

trait Pointed: Functor {
    fn wrap(t: T) -> Self::Wrapped;
}

impl Pointed for Option {
    fn wrap(t: T) -> Self::Wrapped {
        Some(t)
    }
}

 

这里需要注意的是: 我根本没有在Option实现中使用A类型参数。

还有一点值得注意。调用wrap的结果返回的是Self::Wrapped。关于Self::Wrapped,到底是什么?从之前Functor的定义中,确切地知道一件事:Wrapped必须是一个Functor的。








审核编辑:刘清

打开APP阅读更多精彩内容

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分