生成AI と人のプログラミング業務における住み分け


1.AIについて考えていること

私自身はAI にプログラミング業務を完全に任せることは難しいのではないかと感じています。
それは私が知っているレベルの機械学習では、誤差を小さくするという計算を元にしているからです。
(別のアルゴリズムを使っているかもしれませんが)
そのため答えがそこまで厳密に存在しない分野では強いと思いますが、プログラミングの場合ビルドが通らないということや、数学であれば綺麗な解が出ないといったことが起きるのではないかなと専門家ではないので思っています。
以下の記事からも数学が苦手な感じを覚えました。

そこでどういったところをAIに任せて、人は何をすべきかという感触を得たいなと思いました。
また現時点ではAIに依存しすぎるのもリスクかもしれないと考えています。
それは電力を食いすぎるので、利用料が上がったり、色々な制限がかかるリスクも考えておいた方がよいのかなと考えているためです。
省電力の技術も開発されていくとは思いますが。

AIという技術に可能性はもちろん感じています。
自動運転の分野でもセンサー等のフィードバックを受けながら、人より安全な運転を実現しうると思っていますし、人手不足の点を補ってくれるということもあると思います。
ただ完全に依存しきれるといったものでもないような気がしています。

2.試したこと

(1)目的のインプット

算数・数学関連の記事を書いているので、二次関数のグラフや図形を描画するRustのプログラミングを作成したいと考えました。
この目的のインプットは人が担当することであり続けるだろうと考えてはいます。

(2)調査(Microsoft Copilot)

Microsoft Copilotを使用しています。
(コードならCursorを試せというかもしれませんが..)

[質問]
Rustで図形を描画するのにお勧めのライブラリはありますか。円弧も描けるライブラリがよいです。

[回答]
Rustで図形を描画するためには、多数のライブラリが利用可能です。
お勧めのライブラリとしては、以下のものが挙げられます。

Plotters: Rustのドローイングライブラリで、簡単に図形やグラフを描画することができます。
WebAssembly含めて複数のBackendに拡張していける点などが優れています.
nannou: Rustで作成されたクリエイティブコーディング用のフレームワークで、線、多角形、円などの基本的な図形はもちろん、スライダーやXYパッドなどのUIも利用可能です.
imageproc: Rustの画像処理ライブラリで、円や線、文字を描画した画像を生成することができます.
egui: Rustで作成されたGUIライブラリで、線、円、四角形などの図形を描画することができます4
これらのライブラリは、円弧の描画もサポートしています。
どのライブラリを選択するかは、プロジェクトの要件に応じて決定する必要があります。

(感想)
リンクが入っている点はいいなと思いました。こういったケースでは検索をこれまでしていましたが、回答ベースになっているAIを利用するのもありだと思います。
ただこれは他に既に調べている人がいてそれを参考にしているようにも見えました。

(3)ライブラリーの導入

まず導入手順を聞いてみました。
[質問]
Rust の Plotters を導入する際にCargo.tomlに記載することと、導入するために打つコマンドを教えてください。

[回答]
RustのPlottersを導入するには、以下の手順を実行してください。

1.Cargo.tomlに以下の行を追加します。
[dependencies]
plotters = "0.3"

2.コマンドラインで以下のコマンドを実行します。
cargo build

これで、PlottersがRustプロジェクトに導入されます。12

なお、Cargo.tomlに記載する内容は、以下のようになります。

[dependencies]
plotters = "0.3"

以上、ご参考になれば幸いです。

(感想)

いちいち調べなくてもよいのでかなり便利だと感じました。gitのコマンドとかも調べると楽かもしれません。

(4)実装

○ 二次関数のグラフ出力

始めてのライブラリーなので、とりあえずの例を確認してみました。

[質問]
Rust のPlottersで二次関数のグラフをpngファイルで出力するプログラミングの例を教えてください。

[回答]

以下のRustプログラムは、Plottersを使用して二次関数のグラフをPNGファイルに出力する例です。
このプログラムを実行するには、RustのプロジェクトにPlottersを導入する必要があります。
導入手順は前回の回答を参照してください。
Rust

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("quadratic.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("Quadratic Function", ("sans-serif", 30))
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(-10f32..10f32, -10f32..10f32)?;

    chart.configure_mesh().draw()?;

    chart.draw_series(LineSeries::new(
        (-100..=100).map(|x| (x as f32 / 10.0, x as f32 / 10.0 * x as f32 / 10.0)),
        &RED,
    ))?;

    Ok(())
}

このプログラムは、x軸が-10から10まで、y軸が-10から10までの範囲で、二次関数y=x^2のグラフを描画し、quadratic.pngという名前のPNGファイルに保存します。

以上、ご参考になれば幸いです。

(感想)

実行してみました。

確かに大体やってみたいことになっています。
そこでx軸とy軸を青くするというカスタマイズを依頼してみました。

[質問]

上記のプログラムをx軸とy軸を青い線にするプログラムに変更してください。

[回答]

(略記)
上記のプログラムを変更して、x軸とy軸を青い線にするには、以下のようにします。

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("quadratic.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("Quadratic Function", ("sans-serif", 30))
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(-10f32..10f32, -10f32..10f32)?;

    chart.configure_mesh().draw()?;

    chart
        .configure_series_labels()
        .border_style(&BLUE)
        .draw()?;

    chart
        .configure_axes()
        .draw()?;

    Ok(())
}

(感想)

やったみましたが、configure_axes()でエラーが出てビルドが失敗します。
そのため自分で発想を変換して以下のように変えてみました。

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("quadratic.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("Quadratic Function", ("sans-serif", 30))
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(-10f32..10f32, -10f32..10f32)?;

    chart.configure_mesh().draw()?;

    chart.draw_series(LineSeries::new(
        (-100..=100).map(|x| (x as f32 / 10.0, x as f32 / 10.0 * x as f32 / 10.0)),
        &RED,
    ))?;

    //x軸
    chart.draw_series(LineSeries::new(
        (-100..=100).map(|x| (x as f32 / 10.0, 0.0 * x as f32 / 10.0)),
        &BLUE,
    ))?;

    //y軸
    chart.draw_series(LineSeries::new(
        (-100..=100).map(|y| (0.0 * y as f32 / 10.0, y as f32 / 10.0)),
        &BLUE,
    ))?;

    Ok(())
}

<実行結果>


実行できなかった時のデバックや設計は依然として人の仕事のようです。

○ 命名で使う

[質問]
Rustで二次関数の画像を作成する関数名はどのようにするのがよいでしょうか。

[回答]
Rustで二次関数の画像を作成する関数名は、プログラムの目的に応じて適切な名前を付けることが望ましいです。例えば、二次関数のグラフを描画する関数を作成する場合、以下のような名前を付けることができます。

<Rust>

fn draw_quadratic_function() -> Result<(), Box<dyn std::error::Error>> {
// 二次関数のグラフを描画する処理
Ok(())
}

[感想]
意外と名前で悩むときがあるので、AIに任せるのはありかもしれません。

○ 円弧を描く

[質問]
Rust の Plotters で円弧を描くプログラミングの例を教えてください。

[答え]
以下のコードは、RustのPlottersを使用して円弧を描画する方法を示しています。
この例では、半径が100ピクセルの円弧を描画します。
<Rust>

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("arc.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("Arc", ("sans-serif", 50))
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(-150f32..150f32, -150f32..150f32)?;

    let arc = Path::new(vec![
        (0.0, 0.0),
        (100.0, 0.0),
        (100.0, 100.0),
    ])
    .transform(
        Translate::new((-100.0, -100.0))
            .then(Rotate::new(45.0))
            .then(Translate::new((100.0, 100.0))),
    );

    chart.draw_series(std::iter::once(
        arc.stroke_color(&BLACK).stroke_width(2),
    ))?;

    Ok(())
}

このコードは、640x480ピクセルのキャンバスに、半径が100ピクセルの円弧を描画します。
円弧は、中心座標が(100,100)で、開始角度が0度、終了角度が45度の範囲に描画されます。
円弧の線幅は2ピクセルで、黒色で描画されます。

[感想]
Path::new でエラーが出力されビルドできませんでした。
これもコードを変形して以下のようにしました。

use plotters::prelude::*;
use std::f32::consts::PI;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // グラフの描画先を設定
    let root = BitMapBackend::new("arc.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(-150f32..150f32, -150f32..150f32)?;

    //円弧
    chart.draw_series(LineSeries::new(
        (0..=1000).map(|x| {
            (
                100.0 * (((x as f32) / 2000.0 * PI).cos()),
                100.0 * (((x as f32) / 2000.0 * PI).sin()),
            )
        }),
        &RED,
    ))?;

    //y軸の線
    chart.draw_series(LineSeries::new(
        (0..=1000).map(|x| ((x as f32) * 0.0, 100.0 * ((x as f32) / 1000.0))),
        &RED,
    ))?;

    //x軸の線
    chart.draw_series(LineSeries::new(
        (0..=1000).map(|x| (100.0 * (x as f32) / 1000.0, (x as f32) * 0.0)),
        &RED,
    ))?;

    Ok(())
}

実行結果は以下のようになります。

(5)テスト

試しにコードを作成してテストを作成してもらいました。

[質問]
このコードを単体テストするコードを書いてください。

fn alert_humidity(temp: i32) -> String {
    if temp <= 45 {
        "湿度をあげてください".to_string()
    } else {
        "充分な湿度です".to_string()
    }
}

[回答]
もちろんです!以下は、alert_humidity関数を単体テストするRustのコード例です。
このテストは、temp引数に対して適切なメッセージが返されるかどうかを確認します。

fn alert_humidity(temp: i32) -> String {
    if temp <= 45 {
        "湿度をあげてください".to_string()
    } else {
        "充分な湿度です".to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_alert_humidity_low_temp() {
        // 低い温度の場合
        let result = alert_humidity(40);
        assert_eq!(result, "湿度をあげてください");
    }

    #[test]
    fn test_alert_humidity_high_temp() {
        // 高い温度の場合
        let result = alert_humidity(50);
        assert_eq!(result, "充分な湿度です");
    }
}

このテストでは、alert_humidity関数が正しく動作するかどうかを確認しています。
テストケースとして、低い温度(40)と高い温度(50)を用意しています。

テストを実行するには、以下のコマンドを実行してください。

cargo test

テストが成功することを確認してください。

[感想]
テスト自体は成功しました。
単純なコードではありますが、可能性を感じました。

○ 単体テストからコードを作成できるか
[質問]
以下のRustのテストコードからalert_humidity()という名前の関数を実装してください。
(上記のテストコード)

[回答]

fn alert_humidity(temperature: i32) -> &'static str {
    if temperature < 45 {
        "湿度をあげてください"
    } else {
        "充分な湿度です"
    }
}

[感想]
ここにかなり可能性を感じたので、より実用性を挙げて自分が過去に作成したテストコードからコードを生成できるかやってみました。

○ より実践的に
[質問]
以下のRustのコードのテストコードを書いてください。

use regex::Regex;

#[derive(PartialEq, Eq, Hash)]
pub struct Word {
    pub contents: String,
}

impl Word {
    pub fn new(text: &str) -> Self {
        let word = text.to_lowercase();
        Self { contents: word }
    }

    pub fn can_add(&self) -> bool {
        let re = Regex::new(r"\d+").unwrap();
        if re.is_match(self.contents.as_str())
            || self.contents.contains("'")
            || self.contents.contains("’")
        {
            return false;
        }
        true
    }

[回答]
もちろんです!以下は、提供されたRustのコードに対してテストコードを追加したものです。このテストコードは、Word構造体のメソッドであるcan_add()が正しく動作するかどうかを確認します。

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_can_add_no_digit() {
        // 数字が含まれていない場合
        let word = Word::new("apple");
        assert_eq!(word.can_add(), true);
    }

    #[test]
    fn test_can_add_with_digit() {
        // 数字が含まれている場合
        let word = Word::new("banana123");
        assert_eq!(word.can_add(), false);
    }

    #[test]
    fn test_can_add_with_apostrophe() {
        // アポストロフィが含まれている場合
        let word = Word::new("don't");
        assert_eq!(word.can_add(), false);
    }

    #[test]
    fn test_can_add_with_smart_apostrophe() {
        // スマートアポストロフィが含まれている場合
        let word = Word::new("won’t");
        assert_eq!(word.can_add(), false);
    }
}

このテストコードは、以下のケースをカバーしています:

  1. 数字が含まれていない場合 (test_can_add_no_digit)

  2. 数字が含まれている場合 (test_can_add_with_digit)

  3. アポストロフィが含まれている場合 (test_can_add_with_apostrophe)

  4. スマートアポストロフィが含まれている場合 (test_can_add_with_smart_apostrophe)

これらのテストを実行して、Word構造体の挙動が正しいことを確認してください!🔍🦀

[感想]
テストは通りましたし、かなり意図を解釈したテストコードを書いてくれています。
new で小文字にしているのでそこも検討したテストコードがあるといいなとは思いました。
それでは逆もみてみたいと思います。

[質問]
以下のRustのテストコードから構造体Wordとメソッドのcan_add()を作成してください。

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_can_add_no_digit() {
        // 数字が含まれていない場合
        let word = Word::new("apple");
        assert_eq!(word.can_add(), true);
    }

    #[test]
    fn test_can_add_with_digit() {
        // 数字が含まれている場合
        let word = Word::new("banana123");
        assert_eq!(word.can_add(), false);
    }

    #[test]
    fn test_can_add_with_apostrophe() {
        // アポストロフィが含まれている場合
        let word = Word::new("don't");
        assert_eq!(word.can_add(), false);
    }

    #[test]
    fn test_can_add_with_smart_apostrophe() {
        // スマートアポストロフィが含まれている場合
        let word = Word::new("won’t");
        assert_eq!(word.can_add(), false);
    }
}

[回答]
もちろんです!以下に、構造体 Word とメソッド can_add() を作成したサンプルコードを示します。

struct Word {
    value: String,
}

impl Word {
    fn new(value: &str) -> Self {
        Word {
            value: value.to_string(),
        }
    }

    fn can_add(&self) -> bool {
        // 数字が含まれていないか、アポストロフィが含まれていない場合に true を返す
        !self.value.chars().any(|c| c.is_digit(10)) && !self.value.contains("'")
    }
}

[感想]
スマートアプストロフィーを考慮していないので、4番目のテストで失敗しています。
おしいところまでは作れそうだなと感じました。

3.全体を通して感じたこと

今後の進化次第ではもう少し任せられるところが増えそうだなと感じていますが、現時点ではAI には調査の部分やコマンドの確認、単体テストを介した大枠の作成が任せられそうだなと感じました。

(1)どういったプログラムが欲しいか考えることは、人の仕事であり続けるだろうと考えています。
(2)調査に関してはAIに可能性を感じています。
(3)設計は人の仕事かなと思いますが、関数や構造体(クラス)の名前等はAIにアイディアをもらってもよいかと思います。
(4)実装に関しては、0から自然言語を元に作成してもらう場合は、良いサンプルがネット上にある場合にのみ有効かと思います。
単体テストを元に作成してもらう場合は、おしいところまでは作れそうな感触です。
単体テストを作成して、それを元に大まかに作成してもらい、それを元に修正するみたいな作り方もテスト駆動開発と相性がいいかもしれないなと感じました。
上手くいかない場合のデバックは人の仕事だと感じました。

この記事が参加している募集

AIとやってみた

この記事が気に入ったらサポートをしてみませんか?