Ⓜ️

Rust1.75で追加された機能を使ってモナドを作ってみる

2024/01/14に公開1

Rust1.74でモナドを作ってみようとしてたのですが、今回はその続きになります。
https://zenn.dev/hanao/articles/b16b7460e801f5

なんで続きをやろうと思ったの?

Rust1.74で導入された以下の機能を使って、モナドを作ろうとしたのですがその時は残念ながらできませんでした。(詳細はこちらを参照ください。)

その時は、traitの中で -> impl Traitをすることができなかったので断念したのですが、Rust1.75で導入された以下の機能を使えばできそうに見えました。
https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html

そのため、この機能を使って再チャレンジしようとしたのがこの記事になります。

この機能で何ができるようになるのか

詳細は上記blogを見ていただくとして、ざっくり説明すると、Trait内で-> impl Traitを定義することができるようになります。
上記blogからの引用ですが、こんな感じです。

trait Container {
    fn items(&self) -> impl Iterator<Item = Widget>;
}

impl Container for MyContainer {
    fn items(&self) -> impl Iterator<Item = Widget> {
        self.items.iter().cloned()
    }
}

さらに、これで-> impl Futureを定義することもできるようになるため、trait内でasync関数も定義できるようになります。

trait HttpService {
    async fn fetch(&self, url: Url) -> HtmlBody;
//  ^^^^^^^^ desugars to:
//  fn fetch(&self, url: Url) -> impl Future<Output = HtmlBody>;
}

モナドを作ってみる

早速ですが、モナドを実装して行きます。
前回と同様、Functor, Applicative, Monadの型クラスを定義して行きます。

Functor

pub trait Functor {
    type Inner;

    fn fmap<F, B>(self, f: F) -> impl Functor<Inner = B>
    where
        F: FnOnce(Self::Inner) -> B;
}

Applicative

pub trait Applicative: Functor {
    fn pure(a: Self::Inner) -> impl Functor<Inner = Self::Inner>;
    // HaskellのApplicativeの(<*>)と同じ
    fn apply<F, B>(self, f: impl Functor<Inner = F>) -> impl Functor<Inner = B>
    where
        F: FnOnce(Self::Inner) -> B;
}

Monad

pub trait Monad: Applicative {
    fn bind<F, B>(self, f: F) -> impl Monad<Inner = B>
    where
        F: FnOnce(Self::Inner) -> impl Monad<Inner = Self::Inner>;
    fn return_m(a: Self::Inner) -> impl Monad<Inner = Self::Inner> {
        Self::pure(a)
    }
}

Functor, Applicativeは問題なさそうでしたが、Monadのところでこんなエラーが出てしまいました。

error[E0562]: `impl Trait` only allowed in function and inherent method argument and return types, not in `Fn` trait return types
  --> src/hkt_impl.rs:20:35
   |
20 | ...ner) -> impl Monad<Inner = Self::Inner>;
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the trait bound `impl Functor<Inner = <Self as Functor>::Inner>: Monad` is not satisfied
  --> src/hkt_impl.rs:21:36
   |
21 | ...n return_m(a: Self::Inner) -> impl Monad<Inner = Self::Inner...
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Monad` is not implemented for `impl Functor<Inner = <Self as Functor>::Inner>`
22 | ...   Self::pure(a)
   |       ------------- return type was inferred to be `impl Functor<Inner = <Self as Functor>::Inner>` here

Some errors have detailed explanations: E0277, E0562.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `rust-hkt` (lib) due to 2 previous errors

一つひとつ見て行きましょう。

error[E0562]: `impl Trait` only allowed in function and inherent method argument and return types, not in `Fn` trait return types
  --> src/hkt_impl.rs:20:35
   |
20 | ...ner) -> impl Monad<Inner = Self::Inner>;
   |        

こちらをみる感じ、Fntraitのreturn typeに-> impl Traitは許されていなさそうです。

2つ目はこちら

error[E0277]: the trait bound `impl Functor<Inner = <Self as Functor>::Inner>: Monad` is not satisfied
  --> src/hkt_impl.rs:21:36
   |
21 | ...n return_m(a: Self::Inner) -> impl Monad<Inner = Self::Inner...
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Monad` is not implemented for `impl Functor<Inner = <Self as Functor>::Inner>`
22 | ...   Self::pure(a)
   |       ------------- return type was inferred to be `impl Functor<Inner = <Self as Functor>::Inner>` here

Monad traitがimpl Functor<Inner = <Self as Functor>::Inner>をimplしてないと言っています。
Monad traitはFunctorのsub traitなので推論してくれると思ったのですが、できない様ですね。

ここでMonadを以下のように実装するとコンパイルが通るのですが、

pub trait Monad: Applicative {
    fn bind<F, B>(self, f: F) -> impl Monad<Inner = B>
    where
        F: FnOnce(Self::Inner) -> Self::Inner;
    fn return_m(a: Self::Inner) -> impl Functor<Inner = Self::Inner> {
        Self::pure(a)
    }
}
  • bindに与える関数fのシグネチャが変わってSelf::Innerを返してしまう
  • return_mimpl Functorを返してしまうためMonadではなくFunctorApplicativeを返してしまう

ことからモナドとは言えなさそうです。

結論

新しい機能でかなりモナドに近づいた印象がありますが、まだモナドは難しそうです。
(そもそもRustにモナドを実現する意思はあるのでしょうか…??)

Discussion

Naughie(なっふぃ)Naughie(なっふぃ)

bind() に関しては次のように FnOnce の return type をジェネリクスにすれば動きますね.

pub trait Monad: Applicative {
    fn bind<F, B, M>(self, f: F) -> impl Monad<Inner = B>
    where
        F: FnOnce(Self::Inner) -> M,
        M: Monad<Inner = Self::Inner>;
    // fn return_m(a: Self::Inner) -> impl Monad<Inner = Self::Inner>;
}

return_m() に関しては,「Monadimpl Functor を impl していない」ではなく「impl FunctorMonad を impl していない」です.
つまり,「Self::pure(a) の型は impl Functor であって impl Monad ではない」=「Self::pure(a) の型が bind()/return_m() 関数を持っているとは限らない」ということを言っています.

なので「Self::pure(a) の型が Monad でもある」という trait bound が必要なんですが……現行の Rust では難しそうですね〜