<style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.6; letter-spacing: normal; text-shadow: none; word-wrap: break-word; color: #444; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;} .reveal h1, .reveal h2, .reveal h3 {color: #2980b9;} .reveal th {background: #DDD;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal table {margin: 1.0vw auto;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* table design */ .reveal table {background: #f5f5f5;} .reveal th {background: #444; color: #fff;} .reveal td {position: relative; transition: all 300ms;} .reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;} .reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 5.0vw;} .reveal h2 {font-size: 4.0vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 2.4vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;} .reveal code {font-size: 1.6vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h2 { padding: 0 1.5vw; margin: 0.0vw 0 2.0vw -2.0vw; border-left: solid 1.2vw #2980b9; border-bottom: solid 0.8vw #d7d7d7; } </style> # 第13回  ## 第13章 関数型言語の機能 イテレータとクロージャ ### 2020/10/30 原山和之 --- ## 本日のメニュー 1. クロージャ、変数に保存できる関数に似た文法要素 1. イテレータ、一連の要素を処理する方法 1. これら2つの機能を使用して第12章の入出力プロジェクトを改善する方法 [](クロージャとイテレータをマスターすることは、慣用的で速いRustコードを書くことの重要な部) --- ## クロージャ Rustのクロージャは、変数に保存したり、引数として他の関数に渡すことのできる匿名関数です。ある場所でクロージャを生成し、それから別の文脈でクロージャを呼び出して評価することができます。 関数と異なり、呼び出されたスコープの値をクロージャは、キャプチャすることができます。 [](これらのクロージャの機能がコードの再利用や、動作のカスタマイズを行わせてくれる方法) --- ## 動作の抽象化 ##### とあるアプリのアルゴリズム。。。 アプリユーザの年齢や、 BMI、運動の好み、最近のトレーニング、指定された強弱値などの多くの要因からトレーニングプランを生成するアルゴリズムあるとします。 --- ## 架空のメソッド ###### 処理に2秒かかる強度を返すメソッド ```rust= use std::thread; use std::time::Duration; // 重い計算と仮定する fn simulated_expensive_calculation(intensity: u32) -> u32 { // ゆっくり計算します println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); intensity } ``` --- ## プラン生成 ###### ユーザがトレーニングプランを要求した時にアプリが呼び出すコード ```rust= fn main() { // 簡潔にするために一部ハードコード // ハードコードされたユーザー入力の強弱値 let simulated_user_specified_value = 10; // ハードコードされたランダム値(rand) let simulated_random_number = 7; generate_workout( simulated_user_specified_value, simulated_random_number ); } ``` --- ## ビジネスロジック部分 ```rust= fn generate_workout(intensity: u32, random_number: u32) { if intensity < 25 { println!( // 今日は{}回腕立て伏せをしてください! "Today, do {} pushups!", simulated_expensive_calculation(intensity) ); println!( // 次に、{}回腹筋をしてください! "Next, do {} situps!", simulated_expensive_calculation(intensity) ); } else { if random_number == 3 { // 今日は休憩してください!水分補給を忘れずに! println!("Take a break today! Remember to stay hydrated!"); } else { println!( // 今日は、{}分間走ってください! "Today, run for {} minutes!", simulated_expensive_calculation(intensity) ); } } } ``` --- ## 関数でリファクタリング ```rust= // 重複削除して一つにする fn generate_workout(intensity: u32, random_number: u32) { let expensive_result = simulated_expensive_calculation(intensity); if intensity < 25 { println!( "Today, do {} pushups!", expensive_result ); println!( "Next, do {} situps!", expensive_result ); } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_result ); } } } ``` --- ## クロージャーを使用する ```rust= let expensive_closure = |num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }; ``` []('|num|'は仮引数です。クロージャのお尻には、セミコロンが必要。expensive_closureに値が保存されます。) --- ## クロージャーでリファクタリング ```rust= fn generate_workout(intensity: u32, random_number: u32) { let expensive_closure = |num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }; if intensity < 25 { println!( "Today, do {} pushups!", expensive_closure(intensity)//-> 同じクロージャ1 ); println!( "Next, do {} situps!", expensive_closure(intensity)//-> 同じクロージャ2 ); } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_closure(intensity) ); } } } ``` --- ## クロージャの型推論と注釈 - クロージャでは、fn関数のように引数の型や戻り値の型を注釈する必要はありませんが、 - 変数に型注釈できます ```rust= let expensive_closure = |num: u32| -> u32 { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }; ``` --- ## 関数との比較 - 引数に1を加える関数の定義と、 同じ振る舞いをするクロージャの定義の記法 ```rust= fn add_one_v1 (x: u32) -> u32 { x + 1 } let add_one_v2 = |x: u32| -> u32 { x + 1 }; let add_one_v3 = |x| { x + 1 }; let add_one_v4 = |x| x + 1 ; ``` --- ## エラーになるクロージャ ```rust= let example_closure = |x| x; let s = example_closure(String::from("hello")); let n = example_closure(5); ``` ```rust= error[E0308]: mismatched types --> src/main.rs | | let n = example_closure(5); | ^ expected struct `std::string::String`, found integral variable | = note: expected type `std::string::String` found type `{integer}` ``` --- ## ジェネリック引数とFnトレイトを使用してクロージャ保存 - トレーニング生成アプリの問題 - コードは必要以上の回数(二回呼ばれてる) - 重い計算のクロージャ # 解決方法 - メモ化(memoization)または、 遅延評価(lazy evaluation) --- ## 準備-Cacher構造体 ```rust= struct Cacher<T> where T: Fn(u32) -> u32 { calculation: T, value: Option<u32>, } impl<T> Cacher<T> where T: Fn(u32) -> u32 { fn new(calculation: T) -> Cacher<T> { Cacher { calculation, value: None, } } fn value(&mut self, arg: u32) -> u32 { match self.value { Some(v) => v, None => { let v = (self.calculation)(arg); self.value = Some(v); v }, } } } ``` --- ## Cacherを使用 ```rust= fn generate_workout(intensity: u32, random_number: u32) { let mut expensive_result = Cacher::new(|num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }); if intensity < 25 { println!( "Today, do {} pushups!", expensive_result.value(intensity) ); println!( "Next, do {} situps!", expensive_result.value(intensity) ); } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_result.value(intensity) ); } } } ``` --- ## Cacherの限界 - 再利用することを困難にしてしまう問題 ```rust= #[test] fn call_with_different_values() { let mut c = Cacher::new(|a| a); let v1 = c.value(1); let v2 = c.value(2); assert_eq!(v2, 2); } ``` ```rust= thread 'call_with_different_values' panicked at 'assertion failed: `(left == right)` left: `1`, right: `2`', src/main.rs ``` - 単独の値ではなく、ハッシュマップを保持するようにCacherを改変 --- ## クロージャの機能 - クロージャーには、関数にない機能がある - 環境をキャプチャし、 自分が定義されたスコープの変数にアクセスできる ```rust= fn main() { let x = 4; let equal_to_x = |z| z == x; let y = 4; assert!(equal_to_x(y)); } ``` [](xはequal_to_xの引数でもないのに、equal_to_xが定義されているのと同じスコープで定義されているx変数をequal_to_xクロージャは使用できています。) --- ## イテレータ生成 ```rust= let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); ``` - イテレータを使い込んで消費するメソッドを呼ぶまで何の効果もない。 [](Vec<T>に定義されたiterメソッドを呼ぶことでv1ベクタの要素に対するイテレータを生成) --- ## イテレータ使用 ```rust= let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { // {}でした println!("Got: {}", val); } ``` --- ## Iteratorトレイトとnextメソッド - 標準ライブラリで定義 ```rust= pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; // デフォルト実装のあるメソッドは省略 // methods with default implementations elided } ``` [](Iteratorトレイトを実装するには、Item型も定義する必要があり、 そして、このItem型がnextメソッドの戻り値の型に使われている。type, Self::Itemは、関連型についての詳細は、第19章) --- ## イテレータを直接呼ぶ - イテレータの消費 ```rust= #[test] fn iterator_demonstration() { let v1 = vec![1, 2, 3]; let mut v1_iter = v1.iter(); assert_eq!(v1_iter.next(), Some(&1)); assert_eq!(v1_iter.next(), Some(&2)); assert_eq!(v1_iter.next(), Some(&3)); assert_eq!(v1_iter.next(), None); } ``` [](このコードはイテレータを消費、または使い込むの、nextの各呼び出しは、イテレータの要素を一つ、食います。所有権をうばう。) --- ## イテレータの消費 - 標準ライブラリにデフォルトメソッドがある。 ```rust= #[test] fn iterator_sum() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); let total: i32 = v1_iter.sum(); assert_eq!(total, 6); } ``` --- ## イテレータアダプタ - 新しいイテレータを生成 ```rust= let v1: Vec<i32> = vec![1, 2, 3]; v1.iter().map(|x| x + 1); ``` [](警告が出ます。新しく生成したけど使ってないよ的な警告です。) --- ## イテレータアダプタ - 警告なし ```rust= let v1: Vec<i32> = vec![1, 2, 3]; let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]); ``` --- ## イテレータとクロージャ ```rust= #[derive(PartialEq, Debug)] struct Shoe { size: u32, style: String, } fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> { shoes.into_iter() .filter(|s| s.size == shoe_size) .collect() } #[test] fn filters_by_size() { let shoes = vec![ Shoe { size: 10, style: String::from("sneaker") }, Shoe { size: 13, style: String::from("sandal") }, Shoe { size: 10, style: String::from("boot") }, ]; let in_my_size = shoes_in_my_size(shoes, 10); assert_eq!( in_my_size, vec![ Shoe { size: 10, style: String::from("sneaker") }, Shoe { size: 10, style: String::from("boot") }, ] ); } ``` --- ## Iteratorトレイトで独自のイテレータを作成 - 構造体 - 絶対に1から5をカウントするだけのイテレータ ```rust= struct Counter { count: u32, } impl Counter { fn new() -> Counter { Counter { count: 0 } } } // イテレータを使用したときに起きてほしいことを書く impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { self.count += 1; if self.count < 6 { Some(self.count) } else { None } } } ``` --- ## 他のイテレータメソッドも使用 ```rust= #[test] fn using_other_iterator_trait_methods() { let sum: u32 = Counter::new().zip(Counter::new().skip(1)) .map(|(a, b)| a * b) .filter(|x| x % 3 == 0) .sum(); assert_eq!(18, sum); } ``` [](Counterインスタンスが生成する値を取り、最初の値を飛ばしてから、別のCounterインスタンスが生成する値と一組にし各ペアを掛け算し、3で割り切れる結果だけを残し全結果の値を足し合わせる) --- ## 12章の入出力を改善する-Config - Config構造体があったかと。それを変更。 ```rust= impl Config { pub fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } // ここが問題で非効率 マジックナンバーだしね。。。 let query = args[1].clone(); let filename = args[2].clone(); // ここ追加してる? let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); Ok(Config { query, filename, case_sensitive }) } } ``` [](イテレータを使用してみよう) --- ## 12章の入出力を改善する-main関数 ```rust= fn main() { let args: Vec<String> = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); // --snip-- } ``` ↓ ```rust= fn main() { let config = Config::new(env::args()).unwrap_or_else(|err| { eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); // --snip-- } ``` [](env::args関数は、イテレータを返します イテレータの値をベクタに集結させ、それからスライスをConfig::newに渡すのではなく 今ではenv::argsから返ってくるイテレータの所有権を直接Config::newに渡しています。) --- ## 12章の入出力を改善する-Config ```rust= impl Config { pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> { ``` --- ## Iteratorトレイトメソッドを使う - newメソッド ```rust= impl Config { pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> { args.next(); let query = match args.next() { Some(arg) => arg, // クエリ文字列を取得しませんでした None => return Err("Didn't get a query string"), }; let filename = match args.next() { Some(arg) => arg, // ファイル名を取得しませんでした None => return Err("Didn't get a file name"), }; let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); Ok(Config { query, filename, case_sensitive }) } } ``` [前のやつ](https://doc.rust-jp.rs/book/second-edition/ch12-03-improving-error-handling-and-modularity.html#panic%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%E4%BB%A3%E3%82%8F%E3%82%8A%E3%81%ABnew%E3%81%8B%E3%82%89result%E3%82%92%E8%BF%94%E3%81%99) --- ## Iteratorトレイトメソッドを使う - searchメソッド ```rust= pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { contents.lines() .filter(|line| line.contains(query)) .collect() } ``` [前のやつ](https://doc.rust-jp.rs/book/second-edition/ch12-04-testing-the-librarys-functionality.html#a%E5%90%88%E8%87%B4%E3%81%97%E3%81%9F%E8%A1%8C%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B) --- ## ループVSイテレータ - サー・アーサー・コナン・ドイル(Sir Arthur Conan Doyle)の、 シャーロックホームズの冒険(The Adventures of Sherlock Homes)全体をStringに読み込み、 そのコンテンツでtheという単語を検索することでベンチマークを行いました。 こちらが、forを使用したsearch関数のバージョンと、イテレータを使用したバージョンに関するベンチマーク結果です。 ```rust= test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700) test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200) ``` --- ## ---
{"metaMigratedAt":"2023-06-15T14:54:17.970Z","metaMigratedFrom":"YAML","title":"第13回 第13章 関数型言語の機能イテレータとクロージャ","breaks":true,"description":"Rust","slideOptions":"{\"theme\":\"white\",\"slideNumber\":\"c/t\",\"center\":false,\"transition\":\"none\",\"keyboard\":true}","contributors":"[{\"id\":\"ed5d0581-544f-4aa0-a6ad-2f48be3d325d\",\"add\":16074,\"del\":535}]"}
    399 views