Rust1.75で追加された機能を使ってモナドを作ってみる
Rust1.74でモナドを作ってみようとしてたのですが、今回はその続きになります。
なんで続きをやろうと思ったの?
Rust1.74で導入された以下の機能を使って、モナドを作ろうとしたのですがその時は残念ながらできませんでした。(詳細はこちらを参照ください。)
その時は、traitの中で -> impl Traitをすることができなかったので断念したのですが、Rust1.75で導入された以下の機能を使えばできそうに見えました。
そのため、この機能を使って再チャレンジしようとしたのがこの記事になります。
この機能で何ができるようになるのか
詳細は上記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_mがimpl Functorを返してしまうためMonadではなくFunctorやApplicativeを返してしまう
ことからモナドとは言えなさそうです。
結論
新しい機能でかなりモナドに近づいた印象がありますが、まだモナドは難しそうです。
(そもそもRustにモナドを実現する意思はあるのでしょうか…??)
Discussion
bind()に関しては次のようにFnOnceの return type をジェネリクスにすれば動きますね.return_m()に関しては,「Monadがimpl Functorを impl していない」ではなく「impl FunctorがMonadを impl していない」です.つまり,「
Self::pure(a)の型はimpl Functorであってimpl Monadではない」=「Self::pure(a)の型がbind()/return_m()関数を持っているとは限らない」ということを言っています.なので「
Self::pure(a)の型がMonadでもある」という trait bound が必要なんですが……現行の Rust では難しそうですね〜