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>;
|
こちらをみる感じ、Fn
traitの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 では難しそうですね〜