0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

jf_wN0SrCdH 来源:码小菜 作者:码小菜 2022-11-07 10:20 次阅读

05ad3ac8-5de9-11ed-a3b6-dac502259ad0.jpg

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是这样的:

importscala.language.higherKinds

traitFunctor[F[_]]{
defmap[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>traitMyList[A]{
defhead:A
deftail:MyList[A]
}

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

scala>:k-vMyList
MyList'skindisF[A]
*->*
Thisisatypeconstructor:a1st-order-kindedtype.

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

scala>traitFoo[F[_]]:k-vFoo
Foo'skindisX[F[A]]
(*->*)->*
Thisisatypeconstructorthattakestypeconstructor(s):ahigher-kindedtype.

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

scala>traitFoldable[F[_]]{
|defleftFold[A,B](ob:F[A])(zero:B)(fn:(B,A)=>B):B
|}

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

scala>implicitvallistFoldable:Foldable[List]=newFoldable[List]{
|defleftFold[A,B](ob:List[A])(zero:B)(fn:(B,A)=>B):B={
|ob.foldLeft(zero)(fn)
|}
|}

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

一种更好的写法:

scala>importscala.language.implicitConversions
importscala.language.implicitConversions

scala>implicitclassListFoldableOpt[A](list:List[A])(implicitfold:Foldable[List]){
|defleftFold[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)]
enumMyOption{
Some(A),
None,
}

implMyOption{
fnmapB,B>(self,f:F)->MyOption{
matchself{
MyOption::Some(a)=>MyOption::Some(f(a)),
MyOption::None=>MyOption::None,
}
}
}

#[test]
fntest_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)]
enumMyResult{
Ok(A),
Err(E),
}

implMyResult{
fnmapB,B>(self,f:F)->MyResult{
matchself{
MyResult::Ok(a)=>MyResult::Ok(f(a)),
MyResult::Err(e)=>MyResult::Err(e),
}
}
}

#[test]
fntest_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的实现.

traitNaiveFunctor{
typeT;
fnmap(self,f:F)->Self
where
F:FnMut(Self::T)->Self::T;
}

implNaiveFunctorforOption{
typeT=A;
fnmapA>(self,mutf:F)->Option{
matchself{
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"

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

traitFunctor{
typeUnwrapped;
typeWrapped:Functor;
fnmap(self,f:F)->Self::Wrapped
where
F:FnMut(Self::Unwrapped)->B;
}

说明下上面的写法:

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

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

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

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

map的输出是一个Wrapped

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

implFunctorforOption{
typeUnwrapped=A;
typeWrapped=Option;

fnmapB,B>(self,mutf:F)->OptionwhereF:FnMut(A)->B{
matchself{
Some(x)=>Some(f(x)),
None=>None,
}
}
}

#[test]
fntest_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的类型类远远早于其他语言中关联类型的出现。为了进行比较,先看看它是什么样子。

classFunctorfwhere
map::(a->b)->fa->fb

instanceFunctorOptionwhere
mapfoption=
caseoptionof
Somex->Some(fx)
None->None
traitFunctor[F[_]]{
defmap[A,B](fa:F[A])(f:A=>B):F[B]
}

把它转换成Rust就是如下:

traitHktFunctor{
fnmapB>(self:Self,f:F)->Self;
}

implHktFunctorforOption{
fnmapB>(self:Option,f:F)->Option{
matchself{
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中的实现。

traitPointed:Functor{
fnwrap(t:T)->Self::Wrapped;
}

implPointedforOption{
fnwrap(t:T)->Self::Wrapped{
Some(t)
}
}

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

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








审核编辑:刘清

  • GAT
    GAT
    +关注

    关注

    0

    文章

    7

    浏览量

    6326
  • API接口
    +关注

    关注

    1

    文章

    82

    浏览量

    10421
  • Rust
    +关注

    关注

    1

    文章

    228

    浏览量

    6549

原文标题:Rust中GAT和高阶类型

文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    高阶滤波器的设计

    高阶滤波器的设计 实现高阶滤波器的方法是把基高阶函数分解成多个二阶因式之积,每个二阶因式用对应的二阶滤波器来实现,将这些二阶滤波器串接起来
    发表于 05-23 15:39 7118次阅读
    <b class='flag-5'>高阶</b>滤波器的设计

    高阶程控滤波器的研究

    高阶程控滤波器的研究作者:李鸣华关键词:巴氏滤波网络,高阶程控滤波器,仪表测试摘要:本文提出了一种基于二阶巴氏滤波网络的高阶程控滤波器的实现原理与方法,在仪表测试及工程应用领域有一定的实用价值。滤波
    发表于 12-05 09:08

    高阶系统的时域分析

    高阶系统的时域分析
    发表于 04-10 20:58

    SAR与ADC不同输入类型

    作者: Amit Kumbasi 今天,我们继续讲解与逐次逼近寄存器 (SAR) 数模转换器 (ADC) 输入类型有关的内容。在之前的部分中,我研究了输入注意事项和SAR ADC之间的性能比较。在这
    发表于 09-11 14:49

    怎样设计一个高阶带阻滤波器

    为什么网上高阶的带通滤波器比比皆是,而带阻滤波器只有最高两阶的?如果有高阶的(最好是四阶的)带阻滤波器请发个设计图 我想学习学习
    发表于 06-09 18:20

    什么是高阶函数?

    高阶函数是将其他函数作为形参,或者以函数作为返回结果。因为在Scala中,函数是***。这个术语可能听起来有点乱,但实际上我们把 以函数作为形参或以函数作为返回结果的函数和方法统称为高阶函数。
    发表于 11-05 06:46

    如何实现一种环路滤波器(高阶)参数的设计?

    数字锁相环是什么?造成PLL异常锁定的原因是什么?如何实现一种环路滤波器(高阶)参数的设计?
    发表于 06-22 06:53

    怎样使用typedef定义一个数据类型

    typedef与#define有哪些不同?怎样使用typedef定义一个数据类型
    发表于 02-25 07:56

    HLS高阶综合的定义及挑战

    HLS高阶综合(high level synthesis)在被广泛使用之前,作为商业技术其实已经存在了20多年。设计团队对于这项技术可以说呈现出两极化的态度:要么坚信它是先进技术之翘楚,要么对其持谨慎怀疑态度。
    发表于 11-04 13:45 3324次阅读

    基于线性网格创建高阶网格

    在 CFD 模拟使用的多种网格生成方法中,高阶网格是一种能够实现精度、分辨率和计算成本平衡的有效方法。高阶网格划分的目标是利用高阶多项式曲线的优势为 CFD 计算创建网格,从而实现在复杂系统环境下提供比线性网格更高的精度。
    的头像 发表于 09-22 10:30 910次阅读

    Rust中GAT和高阶类型

    Rust在类型系统级别上与Haskell,Scala有许多相似之处。
    的头像 发表于 11-07 10:21 1139次阅读

    用户自定义类型

    SystemVerilog还为工程师定义新的数据类型提供了一种机制。用户定义的数据类型允许从现有数据类型创建新的
    的头像 发表于 02-09 14:53 1021次阅读
    用户自<b class='flag-5'>定义</b><b class='flag-5'>类型</b>

    光腔的损耗有哪些类型

    取决于腔的类型和几何尺寸。几何损耗的高低依模式的不同而异,高阶横模损耗大于低阶横模损耗。是非稳腔的主要损耗。
    的头像 发表于 02-25 15:55 1030次阅读

    定义数据类型

    在运算之前我们必须首先定义出数据类型,定义出脚本支持的数据类型,这是运算的基础。 这一小节我们将定义出数据
    的头像 发表于 03-03 10:10 895次阅读

    UHDI及高阶封装技术对检测系统的挑战

    高阶封装技术相关的复杂性增加使含有多种芯片类型及小型化元器件的PCB设计更复杂。此外,在2.5D和3D封装等高阶封装解决方案的推动下,行业朝着更高密度和更小间距的方向发展,对检测设备提出了显著需求。
    发表于 10-23 15:16 394次阅读
    UHDI及<b class='flag-5'>高阶</b>封装技术对检测系统的挑战