他の言語でレイジーシーケンスまたは「ジェネレーター」関数と呼ばれるものを作成するにはどうすればよいですか?
Pythonでは、次の例(Pythonのドキュメントから)のようにyield
を使用して、中間リストのメモリを使用しない方法で反復可能なシーケンスを遅延生成できます。
# a generator that yields items instead of returning a list
def firstn(n):
num = 0
while num < n:
yield num
num += 1
sum_of_first_n = sum(firstn(1000000))
Rustで同様のことを行うにはどうすればよいですか?
Rust 1.0にはジェネレーター関数がないため、 明示的なイテレーター を使用して手動で行う必要があります。
まず、Pythonの例をnext()
メソッドを持つクラスとして書き直します。これは、Rustで取得する可能性のあるモデルに近いためです。次に、書き直すことができます。 in Rust Iterator
特性を実装する構造体を使用します。
同様の結果を達成するためにクロージャを返す関数を使用することもできるかもしれませんが、それをIterator
トレイトに実装させることは不可能だと思います(生成するために呼び出される必要があるため)新しい結果)。
Rust doesジェネレーターはありますが、非常に実験的であり、現在安定したRustでは使用できません。
Range
具体的な例を処理します。 ..
の構文糖衣構文で使用できます。
fn main() {
let sum: u64 = (0..1_000_000).sum();
println!("{}", sum)
}
Range
が存在しなかった場合はどうなりますか?それをモデル化するイテレータを作成できます!
struct MyRange {
start: u64,
end: u64,
}
impl MyRange {
fn new(start: u64, end: u64) -> MyRange {
MyRange {
start: start,
end: end,
}
}
}
impl Iterator for MyRange {
type Item = u64;
fn next(&mut self) -> Option<u64> {
if self.start == self.end {
None
} else {
let result = Some(self.start);
self.start += 1;
result
}
}
}
fn main() {
let sum: u64 = MyRange::new(0, 1_000_000).sum();
println!("{}", sum)
}
内臓は同じですが、Pythonバージョンよりも明示的です。特に、Pythonのジェネレーターは状態を追跡します。Rustは明示性を優先するため、独自の状態を作成して手動で更新する必要があります。重要な部分は、 Iterator
trait の実装です。反復子が特定のタイプの値を生成するように指定します(type Item = u64
)次に、各反復のステップと、反復の終わりに到達したことを確認する方法を扱います。
この例は、ジェネリックスを使用する実際のRange
ほど強力ではありませんが、その方法の例を示しています。
毎晩Rust doesジェネレーターがあります ですが、非常に実験的です。1つを作成するには、いくつかの不安定な機能を取り込む必要があります。ただし、次のように見えますpretty Pythonの例に近いですが、Rust固有の追加がいくつかあります。
#![feature(generators, generator_trait)]
use std::{
ops::{Generator, GeneratorState},
pin::Pin,
};
fn firstn(n: u64) -> impl Generator<Yield = u64, Return = ()> {
move || {
let mut num = 0;
while num < n {
yield num;
num += 1;
}
}
}
現在のRustはすべてイテレーターで動作するため、より広いエコシステムで遊ぶためにジェネレーターをイテレーターに変換するアダプターを作成します。このようなアダプターはに存在すると予想されます。最終的には標準ライブラリ:
struct GeneratorIteratorAdapter<G>(G);
impl<G> Iterator for GeneratorIteratorAdapter<G>
where
G: Generator<Return = ()>,
{
type Item = G::Yield;
fn next(&mut self) -> Option<Self::Item> {
let me = unsafe { Pin::new_unchecked(&mut self.0) };
match me.resume() {
GeneratorState::Yielded(x) => Some(x),
GeneratorState::Complete(_) => None,
}
}
}
これで使用できます。
fn main() {
let generator_iterator = GeneratorIteratorAdapter(firstn(1_000_000));
let sum: u64 = generator_iterator.sum();
println!("{}", sum);
}
これについて興味深いのは、Iterator
の実装よりも強力ではないということです。たとえば、イテレータには size_hint
メソッドがあります。これにより、イテレータの利用者は、残っている要素の数を知ることができます。これにより、コンテナにcollect
ingするときに最適化が可能になります。ジェネレーターにはそのような情報はありません。
安定したRustをサポートする私のスタックフルRust ジェネレータライブラリ を使用できます:
#[macro_use]
extern crate generator;
use generator::{Generator, Gn};
fn firstn(n: usize) -> Generator<'static, (), usize> {
Gn::new_scoped(move |mut s| {
let mut num = 0;
while num < n {
s.yield_(num);
num += 1;
}
done!();
})
}
fn main() {
let sum_of_first_n: usize = firstn(1000000).sum();
println!("sum ={}", sum_of_first_n);
}
またはもっと簡単に:
let n = 100000;
let range = Gn::new_scoped(move |mut s| {
let mut num = 0;
while num < n {
s.yield_(num);
num += 1;
}
done!();
});
let sum: usize = range.sum();