Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 13. Functional Language Features - 1 Closures (1)

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回は、Functional Language FeaturesのClosuresについて学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Functional Language Features: Iterators and Closures

Functional Language Features: Iterators and Closures - The Rust Programming Language

一般的な関数型言語の機能に似たRustの機能としては、下記をカバーしています。

  • Closures : 変数に格納できる関数のような構造
  • Iterators : 一連の要素を処理する方法

また、下記も記載されているそうです。

  • ClosuresIterators を利用したI/Oの改善方法
  • ClosuresIterators のパフォーマンス

Closures: Anonymous Functions that Can Capture Their Environment

Rustの Closures は下記が可能な匿名関数です。

  • 変数への保存
  • 引数として他関数へ渡せる

下記に、公式ドキュメント(Closures: Anonymous Functions that Can Capture Their Environment - The Rust Programming Language)のデモコードを元に、クロージャー利用までの手順を記載致します。

何も考慮せずに書いた場合

simulated_expensive_calculation 関数で約2秒停止するため、 generate_workoutintensity < 25 の場合、約4秒の待機時間が発生します。

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

///
/// 実行に約2秒かかる仮想関数
///
fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    let sec: u64 = 2;
    let duration: Duration = Duration::from_secs(sec);
    thread::sleep(duration);
    intensity
}

///
/// トレーニング計画を出力する関数
///
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)
            );
        }
    }
}

変数の利用

expensive_result の変数に代入することにより、intensity < 25 の場合でも、約2秒の待機時間になります。

しかし、下記の場合、intensity >= 25 && random_number == 3 の場合であっても待機時間が発生してしまいます。

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

///
/// 実行に約2秒かかる仮想関数
///
fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    let sec: u64 = 2;
    let duration: Duration = Duration::from_secs(sec);
    thread::sleep(duration);
    intensity
}

///
/// トレーニング計画を出力する関数
///
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);
        }
    }
}

Closureの利用

無名関数を expensive_closure に代入することで、Closureの宣言が可能です。

ただ、下記のままでは、通常で書いた内容と変わりがありません。

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

///
/// トレーニング計画を出力する関数
///
fn generate_workout(intensity: u32, random_number: u32) {
    // 実行に約2秒かかる仮想クロージャー
    let expensive_closure = |num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure(intensity));
        println!("Next, do {} situps!", expensive_closure(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!("Today, run for {} minutes!", expensive_closure(intensity));
        }
    }
}

Closure x Cacherの利用

struct Cacher<T> としてClosureの結果をキャッシュすることで、無駄な処理がなく、かつ、最大約2秒の処理とすることが可能です。

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

///
/// トレーニング計画を出力する関数
///
fn generate_workout(intensity: u32, random_number: u32) {
    // 実行に約2秒かかるキャッシュを利用した仮想クロージャー
    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)
            );
        }
    }
}

///
/// キャッシュロジック
///
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: Option<u32>,
}

///
/// キャッシュロジック impl
///
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
            }
        }
    }
}

まとめ

github.com

  • let a = |b| { ... } によってClosureを宣言可能

最後に

Closuresにはまだまだ細かく見るべき点が複数ありますが、一旦今回はここまでにします。

明日、Closures続きを記載します。