Rust 中的高級類型

大家好,我是螃蟹哥。

本文分享一篇關於 Rust 中「高級」類型的文章。

介紹

最近開始學習 Rust。在用 C++ 編寫 vulkan raytracer 的過程中,我非常厭倦它乏味的內存管理、頭文件 / 源代碼重複以及最糟糕的 IDE 工具,我不得不找到更好的東西。Rust 一直是我周圍喋喋不休的常見話題,所以我決定看看,爲什麼他們討論這麼激烈。

我注意到的第一件事是 Rust 工具非常有效且易於設置。我以前從來沒有花這麼少的時間爲我的 Neovim 設置添加一門新的語言。

Hello world 和打印一些質數很快。因此準備嘗試處理一些引起我興趣的功能:關聯類型。Rust 是 Haskell 和 C++ 中最好的嗎?

我必須首先提到 Edmund Smith 的這篇文章 [1],我從他那裏竊取了完成這項工作所需的一些核心特徵。但我相信最終我得到了一個更簡單、更優雅的解決方案,因此我認爲這篇文章爲討論增添了一些東西。

更高級的類型

我們需要意識到的第一件事是 Functor 不是在傳統的 “完整” 類型上定義的。相反,它是在類型爲 * -> * 的類型上定義的。一個例子是 Maybe,或者像俄羅斯人那樣稱呼它:Option。看一下 Haskell 中 Functor 的定義:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

我們期望 f 是仍然可以應用另一種類型的類型。這是我們的第一個問題,我們在 Rust 中缺乏這種能力。雖然有添加此功能的建議,但似乎沒有理由懷疑此功能會與核心語言原則發生衝突。

但是,目前我們需要自己破解它。從 Edmund 複製,我決定了兩個特性,它們只實現我們的目的所需的關聯類型魔法。我們實際上不需要未應用的通用 Option 類型,只要我們可以將 Option<A> 轉換爲 Option<B>

爲了比較,以下是我們如何在 haskell 中構建我們需要的類型:

type building haskell

同時我們將如何在 Rust 中做到這一點:

type building in rust

管理特徵:Generic1 和 Plug

爲了促進上述類型的構建 / 破壞,我們需要兩個具有關聯類型的管理特徵(administrative traits)。用戶需要在他們的類型上實現這些 trait,然後他們才能開始實現在更高級的類型上運行的任何其他 trait。

第一個是 Generic1,它允許我們從 Option<A> 中取出 A,或者更確切地說是類型 (I)nside:

trait Generic1 {
    type I;
}

impl<A> Generic1 for Option<A> {
    type I = A;
}

其次,我們需要一種方法來替換 Option 的內部類型。我們使用 trait Plug 來做到這一點:

trait Plug<B> {
    type R;
}

impl<A, B> Plug<B> for Option<A> {
    type R = Option<B>;
}

// In other words
// <Option<A> as Plug<B>>::R == Option<B>

請注意,我們必須相信這裏的用戶會正確實現這些特徵。但可悲的是,類型檢查器現在完全沒有意識到結果類型仍然是 Option 的一個實例,因此通常無法導出表達式的類型。

Functor

有了類型管理特徵,我們可以很容易地定義和實現 Functor 特徵(trait),儘管在語法上有點混亂。

trait Functor {
    fn fmap<B>(&self, f: &dyn Fn(&<Self as Generic1>::I) -> B) -> <Self as Plug<B>>::R
        where Self: Plug<B> + Generic1;
}

impl<A> Functor for Option<A> {
    fn fmap<B>(&self, f: &dyn Fn(&<Self as Generic1>::I) -> B) -> <Self as Plug<B>>::R {
        // Apply the function over the contained value, if there is one
        match self {
            None => None,
            Some(v) => Some(f(v)),
        }
    }
}

Applicative

這個可以說是最難看的。爲清楚起見,讓我還爲你提供更合理的 ap 函數的 Haskellian 類型簽名:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

而 Rust 的代碼:

trait Applicative {
    fn ap<A, B, F: Fn(&A) -> B>(x: &<Self as Plug<F>>::R, arg: &<Self as Plug<A>>::R) -> <Self as Plug<B>>::R
        where Self: Plug<A> + Plug<B> + Plug<F>;

    fn pure(x: Self::I) -> Self
        where Self: Generic1;
}

impl<A> Applicative for Option<A> {
    fn pure(x: <Self as Generic1>::I) -> Self {
        Some(x)
    }

    fn ap<A2,B,F: Fn(&A2) -> B>(x: &<Self as Plug<F>>::R, arg: &<Self as Plug<A2>>::R) -> <Self as Plug<B>>::R {
        match (x, arg) {
            (None, _) => None,
            (_, None) => None,
            (Some(f), Some(v)) => Some(f(v))
        }
    }
}

Monad

最後是 Monad,在 Option 的情況下,這與 Functor 非常相似。唯一的區別是不需要將結果包裝在 Some 中,因爲現在是提供的函數的責任。

trait Monad {
    fn bind<B>(&self, f: &dyn Fn(&Self::I) -> Self::R) -> Self::R 
        where Self: Plug<B> + Generic1;
}

impl<A> Monad for Option<A> {
    fn bind<B>(&self, f: &dyn Fn(&<Self as Generic1>::I) -> <Self as Plug<B>>::R) -> <Self as Plug<B>>::R { 
        match self {
            None => None,
            Some(v) => f(v),
        }
    }
}

如何使用?

讓我們看看是否真的可以使用我們的新特徵(traits)編寫一些代碼。請注意,以下代碼段在作爲有效的 Rust 程序的同時具有儘可能少的類型簽名。

fn main() {
    let x = Some(5);
    let y = x.fmap(&|i| i+1);
    let z = x.bind(&|_| Applicative::pure(String::from("bound!")));

    let x2 : Option<i32>= None;
    let y2 = x2.fmap(&|i| i+1);
    let z2 = x2.bind(&|_| Applicative::pure(String::from("bound!")));

    let g: Option<&dyn Fn(&i32) -> String> = Some(&|i| format!("{}={}", i, i));
    let g_eval = <Option<&dyn Fn(&i32) -> String> as Applicative>::ap(&g, &Some(69));

    println!("x: {:?}, x2: {:?}, y: {:?}, y2: {:?}, z: {:?}, z2: {:?}, geval: {:?}", x, x2, y, y2, z, z2, g_eval);
}

運行得到以下輸出:

x: Some(5), x2: None, y: Some(6), y2: None, z: Some("bound!"), z2: None, geval: Some("69=69")

所以,你可以在 Rust 中使用更高級的類型。但考慮到實現的語法噪音、經常混淆並需要幫助的類型檢查器以及沒有多大意義的錯誤消息,對於大多數項目來說,它肯定不是一個非常有吸引力的選擇。特別是 Rust 的錯誤處理挺不錯的。

原文鏈接:https://hugopeters.me/posts/14/

參考資料

[1]

文章: https://gist.github.com/edmundsmith/855fcf0cb35dd467c29a9350481f0ecf

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/2QE3Ljy2tVXp6FhK0Bi06w