0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Rust中GAT和高階類型

jf_wN0SrCdH ? 來源:Rust語言中文社區(qū) ? 作者:Rust語言中文社區(qū) ? 2022-11-07 10:21 ? 次閱讀

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

Rust在類型系統(tǒng)級別上與Haskell,Scala有許多相似之處。

兩者都有類型(type),泛型類型(generic types),關(guān)聯(lián)類型(associated types)和特質(zhì)/類型類(traits/type classes)(基本上是等效的)。

但是,Haskell有Rust缺乏的一個(gè)特性:高階類型(Higher Kinded Types), 也就是通常說的HKTs。

這不是Rust故意不添加,也不是Rust應(yīng)該填補(bǔ)的一些差距。其實(shí)這是Rust的一個(gè)有意的設(shè)計(jì)。

結(jié)果就是,到?jīng)]有GATs出現(xiàn)時(shí),某些代碼無法真正在Rust中實(shí)現(xiàn)。

以Haskell中的Functor為例。Functor其實(shí)是對類型上實(shí)現(xiàn)的map的一個(gè)更加抽象的實(shí)現(xiàn),不關(guān)心具體類型的一個(gè)接口。

比如,在Scala中,要實(shí)現(xiàn)一個(gè)map的Functor是這樣的:

importscala.language.higherKinds

traitFunctor[F[_]]{
defmap[A,B](fa:F[A])(f:A=>B):F[B]
}

如果你使用過Scala的集合api,這看起來應(yīng)該非常相似。所有集合都可以使用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]類型是一個(gè)一階類型(1st-order-kinded type);任何MyList的類型都是由A參數(shù)化的,可以將MyList看作是一個(gè)類型函數(shù),如A => MyList[A],因此給定一個(gè)類型A,我們可以創(chuàng)建一個(gè)新的類型MyList[A]。如果MyList是一階類型,那么什么是類型化類型呢?其實(shí)想想,什么是高階函數(shù)啊? 它不就是一個(gè)接受函數(shù)然后返回另外另一個(gè)函數(shù)的函數(shù)。同理可得,什么是高階類型呢? 它是由另一個(gè)類型參數(shù)化的類型。我再寫一個(gè)簡單的例子。

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

這里的Foo就是一個(gè)高階類型。它是一個(gè)類型構(gòu)造函數(shù),參數(shù)也是一個(gè)類型構(gòu)造函數(shù)。為了說清楚這點(diǎn),我再寫一個(gè)例子.我寫一個(gè)trait,里面有一個(gè)leftFold的方法。

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

然后呢,我再實(shí)現(xiàn)一個(gè)listFoldable, 這是最常見的吧。

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

上面我定義了一個(gè)適用于任何List類型的Foldable對象。leftFold方法將取A并使用A構(gòu)造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中的很多不同結(jié)構(gòu)都實(shí)現(xiàn)了map函數(shù),比如: Option, Result, Iterator, and Future.

但是, 在Rust里面,還不能編寫可以給多種類型實(shí)現(xiàn)的通用Functortrait, 就像我剛才上面寫的trait Functor[F[_]]。通常呢 在Rust里面是單個(gè)類型單獨(dú)去實(shí)現(xiàn)map。例如,我這里自己寫了一個(gè)MyOption和MyResult枚舉類, 并實(shí)現(xiàn)了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"));
}

上面的幾個(gè)例子都是直接在自己的結(jié)構(gòu)中定義的map方法。如果沒有GATs, 就不可能將map定義為一個(gè)trait的方法。這是為什么呢?下面是一個(gè)在Rust里面簡單的Functortrait實(shí)現(xiàn),以及Option的實(shí)現(xiàn).

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內(nèi)部的值定義了一個(gè)關(guān)聯(lián)類型T。給Optionimpl這個(gè)trait,T=A。這就是問題所在。Rust將T硬編碼為一種類型A,因?yàn)橥ǔT趍ap函數(shù)中,希望返回的類型是B,但在之前版本穩(wěn)定的Rust中,沒有辦法說"我想要一個(gè)既能與NaiveFunctor關(guān)聯(lián)的類型,又要求這個(gè)類型和關(guān)聯(lián)類型有一點(diǎn)不一樣。

這就是泛型關(guān)聯(lián)類型發(fā)揮作用的地方了。

多態(tài)Functor的實(shí)現(xiàn)

為了得到一個(gè)多態(tài)Functor.我想要的是:"我的類型最終應(yīng)該是我在其中包裹的類型".例如,對于Option,我想說的是"如果我有一個(gè)Option,那么它肯定包含一個(gè)A類型,但如果它包含一個(gè)B類型,它將是Option"

這里就需要使用泛型關(guān)聯(lián)類型.

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

說明下上面的寫法:

每個(gè)Functor都有一個(gè)關(guān)聯(lián)的Unwrapped類型.

還有另一個(gè)關(guān)聯(lián)類型Wrapped,它與Self類似,但有一個(gè)不同的包裝值

和前面例子一樣, map接受兩個(gè)參數(shù)的一個(gè)是:self和一個(gè)函數(shù)

函數(shù)形參將從當(dāng)前關(guān)聯(lián)的Unwrapped值映射到一個(gè)新的類型B

map的輸出是一個(gè)Wrapped

有點(diǎn)抽象。具體點(diǎn)現(xiàn)在看下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);
}

編譯通過,非常優(yōu)雅。

類型類

在Haskell和Scala中,其實(shí)是不需要這種泛型關(guān)聯(lián)類型的。事實(shí)上,Haskell中Functors不使用任何關(guān)聯(lián)類型。Haskell中Functor的類型類遠(yuǎn)遠(yuǎn)早于其他語言中關(guān)聯(lián)類型的出現(xiàn)。為了進(jìn)行比較,先看看它是什么樣子。

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]
}

把它轉(zhuǎn)換成Rust就是如下:

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

implHktFunctorforOption{
fnmapB>(self:Option,f:F)->Option{
matchself{
Some(a)=>Some(f(a)),
None=>None,
}
}
}

但是上面的代碼是編譯不通過的。因?yàn)槲以噲D給Self提供類型參數(shù)。但是在Rust中, 單獨(dú)一個(gè)Option不是一個(gè)類型. Option要成為一個(gè)類型,必須有一個(gè)類型參數(shù).比如: Option 是一個(gè)類型. Option卻不是.

相反,在Haskell中,Maybe Int是kind type的一種類型。Maybe是類型構(gòu)造函數(shù),類型為type -> type。但是出于創(chuàng)建類型類和實(shí)例的目的,可以將Maybe處理為具有自己的類型。Haskell中的Functor作用于type->type。

這就是我所說的"高階類型": 就是說我可以寫出擁有類型的類型(Kind)。

Pointer 的實(shí)現(xiàn)

Haskell中有一個(gè)有爭議的類型類叫做Pointed。之所以有爭議的,是因?yàn)樗肓艘粋€(gè)類型類,但沒有與它相關(guān)的任何laws。我想在Rust中實(shí)現(xiàn)下Pointed。

什么是Pointed

Pointed的思想很簡單:將一個(gè)值包裝成一個(gè)類似Functor的東西。比如:在Option的情況下,它就用Some包裝它。在Result中,它使用Ok。對于Vec,它是一個(gè)單值向量。與Functor不同,這里是一個(gè)靜態(tài)方法,因?yàn)槲覀儧]有現(xiàn)有的Point值要改。讓我們看看它在Rust中的實(shí)現(xiàn)。

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

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

這里需要注意的是: 我根本沒有在Option實(shí)現(xiàn)中使用A類型參數(shù)。

還有一點(diǎn)值得注意。調(diào)用wrap的結(jié)果返回的是Self::Wrapped。關(guān)于Self::Wrapped,到底是什么?從之前Functor的定義中,確切地知道一件事:Wrapped必須是一個(gè)Functor的。

本篇文章關(guān)于GATs就先聊到這里。感興趣的同學(xué)可以關(guān)注下公眾號。下篇再見。

審核編輯:湯梓紅

  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4260

    瀏覽量

    62232
  • GAT
    GAT
    +關(guān)注

    關(guān)注

    0

    文章

    7

    瀏覽量

    6323
  • Rust
    +關(guān)注

    關(guān)注

    1

    文章

    228

    瀏覽量

    6526
收藏 人收藏

    評論

    相關(guān)推薦

    Rust的From和Into trait的基礎(chǔ)使用方法和進(jìn)階用法

    、可靠和安全的系統(tǒng)級應(yīng)用的首選語言。 Rust的From和Into是兩個(gè)重要的trait,它們可以幫助我們進(jìn)行類型轉(zhuǎn)換。From trait允許我們從一個(gè)類型轉(zhuǎn)換到另一個(gè)
    的頭像 發(fā)表于 09-20 10:55 ?1655次閱讀

    如何在Rust讀寫文件

    見的內(nèi)存安全問題和數(shù)據(jù)競爭問題。 在Rust,讀寫文件是一項(xiàng)非常常見的任務(wù)。本教程將介紹如何在Rust讀寫文件,包括基礎(chǔ)用法和進(jìn)階用法。 基礎(chǔ)用法 讀取文件內(nèi)容 使用 std::f
    的頭像 發(fā)表于 09-20 10:57 ?1874次閱讀

    Rust的多線程編程概念和使用方法

    Rust是一種強(qiáng)類型、高性能的系統(tǒng)編程語言,其官方文檔強(qiáng)調(diào)了Rust的標(biāo)準(zhǔn)庫具有良好的并發(fā)編程支持。Thread是Rust
    的頭像 發(fā)表于 09-20 11:15 ?879次閱讀

    Windows -編程-數(shù)據(jù)類型

    Windows -編程-數(shù)據(jù)類型Rust 的每個(gè)值都有特定的數(shù)據(jù)類型,它告訴 Rust 指定了什么樣的數(shù)據(jù),以便它知道如何處理這些數(shù)據(jù)。我
    發(fā)表于 08-24 14:30

    RUST在嵌入式開發(fā)的應(yīng)用是什么

    Rust是一種編程語言,它使用戶能夠構(gòu)建可靠、高效的軟件,尤其是用于嵌入式開發(fā)的軟件。它的特點(diǎn)是:高性能:Rust具有驚人的速度和高內(nèi)存利用率。可靠性:在編譯過程可以消除內(nèi)存錯(cuò)誤。生產(chǎn)效率:優(yōu)秀
    發(fā)表于 12-24 08:34

    Rust代碼中加載靜態(tài)庫時(shí),出現(xiàn)錯(cuò)誤 ` rust-lld: error: undefined symbol: malloc `怎么解決?

    時(shí),出現(xiàn)錯(cuò)誤 ` [i]rust-lld: error: undefined symbol: malloc `。如何將這些定義包含在我的靜態(tài)庫?
    發(fā)表于 06-09 08:44

    GAT 聯(lián)柵晶體管

    GAT 聯(lián)柵晶體
    發(fā)表于 11-06 17:07 ?722次閱讀

    聯(lián)柵晶體管(GAT)是什么意思?

    聯(lián)柵晶體管(GAT)是什么意思?  聯(lián)柵晶體管是一種新型功率開關(guān)半導(dǎo)體器件,簡稱GATGAT是介于雙極型晶體管(BJT)和場效應(yīng)晶體管(FET)之間的特種器
    發(fā)表于 03-05 14:35 ?2784次閱讀

    Linux內(nèi)核整合對 Rust 的支持

    Linux Plumbers Conference 2022 大會(huì)上舉行了一個(gè) Rust 相關(guān)的小型會(huì)議,該會(huì)議討論的大方向大致為:正在進(jìn)行的使 Rust 成為一種合適的系統(tǒng)編程語言的工作,以及在主線 Linux 內(nèi)核整合對
    的頭像 發(fā)表于 09-19 11:06 ?1138次閱讀

    Rust原子類型和內(nèi)存排序

    原子類型在構(gòu)建無鎖數(shù)據(jù)結(jié)構(gòu),跨線程共享數(shù)據(jù),線程間同步等多線程并發(fā)編程場景起到至關(guān)重要的作用。本文將從Rust提供的原子類型和原子類型的內(nèi)
    的頭像 發(fā)表于 10-31 09:21 ?874次閱讀

    什么是高階類型???如何去定義高階類型

    這不是Rust故意不添加,也不是Rust應(yīng)該填補(bǔ)的一些差距。 其實(shí)這是Rust的一個(gè)有意的設(shè)計(jì)。
    的頭像 發(fā)表于 11-07 10:20 ?1094次閱讀

    Rust的錯(cuò)誤處理方法

    Rust 沒有提供類似于 Java、C++ 的 Exception 機(jī)制,而是使用 Result 枚舉的方式來實(shí)現(xiàn)。
    的頭像 發(fā)表于 02-20 09:37 ?880次閱讀

    rust語言基礎(chǔ)學(xué)習(xí): Default trait

    Default trait 顧名思義是默認(rèn)值,即Rust為特定類型實(shí)現(xiàn) Default 特性時(shí),可以為該類型賦予了可選的默認(rèn)值。
    的頭像 發(fā)表于 05-22 16:16 ?1175次閱讀

    rust語言基礎(chǔ)學(xué)習(xí): rust的錯(cuò)誤處理

    錯(cuò)誤是軟件不可避免的,所以 Rust 有一些處理出錯(cuò)情況的特性。在許多情況下,Rust 要求你承認(rèn)錯(cuò)誤的可能性,并在你的代碼編譯前采取一些行動(dòng)。
    的頭像 發(fā)表于 05-22 16:28 ?1954次閱讀

    RustPin/Unpin詳解

    對我來說,其中之一就是在Rust Pin/Unpin 。
    的頭像 發(fā)表于 07-20 11:00 ?799次閱讀