プログラム言語の型が何故前置が多かったのか?

結論から言うと、今となっては型の記述は後置のほうがやりやすいです。これは、型推論の事情から後置表現じゃないとうまくいきません。

例えば、C/C++ の場合、型を前に書きます。

int x, y;
int sum ;

逆に Pascal を始めとして、次のように後置になるものが最近の傾向です。

var x, y: Integer;
var sum: Integer;

一見すると、どちらでも変わらないような気がするのですが、ここに C++ のイテレータを書くと

varctor<int> vec = {1,2,3};
std::vector<int>::iterator it = vec.begin();

いや、これはもう無理です。まあ、実際こう書いていたわけですが、ちょっとインテリセンスとか何かの IDE の助けがないと難しいです。イテレータぐらいだから大丈夫だけど、C# のややこしい LINQ のポインタだとか諸々を加えると無理です。なので、型推論させて、C++ のほうは auto とか、C# の場合は var で受けるのが常です。

varctor<int> vec = {1,2,3};
auto it = vec.begin();

と型推論をさせます。ハッキリ言って、C/C++ でも後置型に変えてしまって、

auto x = 10 : int ;
auto y = 20 : int ;

だろうとは思います。実際のところ auto が使えるので、下記のようにキャストを使って無理矢理推論させることが可能です。あまり意味がありませんが。

auto x = (int)10 ;
auto y = (int)20 ;

C言語以前は、前置型であったのか?

C 言語以前が、前置型だったのか、とは思っていたのですが、これが違います。Ada が後置型で、なんと COBOL が後置型です。

– Fortran 1957 年  前置型
– COBOL 1959 年    後置型
– Pascal 1970 年代  後置型
– C 言語 1972 年   前置型
– Ada 1980 年代    後置型
– Java 1995 年     前置型

ここで、調べたときに「ああ、これ、前置型とか後置型とかはプログラム言語を作った人の趣味だわ…」と気づいたわけで、何か意図があって指定したわけではなさそうです。

ただ、Pascal が教育言語として出て来たときには、「C 言語のように前置きで型指定をするのではなく、教育的に理解しやすいように Pascal は型の指定が後置にしてある」という話があったので、何等かの理由があって、C 言語以前は前置きが多かったのではないか?と思っていたわけです。

これ、実際にコンパイラを作ろうとすると型の指定が前のほうにあったほうが楽で(これも人に依るとは思うのですが)、後にあるときはちょっと面倒なんですよね。あと、変数の前に var とか let とかあるのが無意味では?と思ったものです。まあ、Basic なんて Dim で定義したりするので、型はないのですが「変数であること」宣言みたいなものが var/let/Dim には存在します。

構文解析のときに前置型と後置型でメモリ使用量などに差異はでるのか?

ここからが本題で、例えばコンパイラを作ろうとしたときに何等かのメモリの制約などに違いがでるのか?という視点で見ていきます。今となっては、メモリが潤沢にあるのでどちらでも構わないのですが、歴史上「32kB 程度で動くことを想定」したときに差異がでていたのかもしれません。

ただし、これも結論を先に言えば、COBOL が後置型なので、仮説としても弱いです。

agnet.md を作成する

この手の実験をするのに、AI エージェントはとても役に立ちます。かつては、上記のことを証明しようと思ったら疑似コンパイラをちまちまとプログラミングして比較しないといけないところなのですが、バイブコーディングでかなりイイ線までいってくれます。

疑似的にコンパイラを作ればいいので、字句解析と構文解析だけ作ります。想定としては変数の型指定のところだけなので、変数定義と関数定義だけを例にとっています。もう少し話を広げれば構造体とかも含めてもいいかもしれません。あと、C 言語風のポインタ指定とかも入れるとよいでしょう。

プログラム言語は、C言語、Pascal、Fortran、COBOL、Ada、Java、Kotlin、Lisp を対象にしています。最初は、C言語、Pascal だけ作って後から少しずつ増やしていきました。

# コンパイル時の使用メモリ量をチェックする

プログラム言語には、型指定を前置と後置するものがある。

例えば、C言語やJavaは前置型指定であり、変数宣言時に型を指定する。

```c
int x = 10;
```

一方、Pascal や Kotlin などは後置型指定となる。

```pascal
var x: Integer;
x := 10;
```

プログラム言語の歴史上、前置型指定が主流であったが、後置型指定も徐々に普及してきている。
前置型指定は、コンパイルするときに型の指定が前置のほうがメモリ使用量が少なかったのではないか?と仮説を立てる。
これを検証する。

C 言語以前には、Ada が後置型であるので、Pascal 以前にも後置型があってもおかしくはない

```Ada
X : Integer := 10;
```

## 登場時期

- Fortran 1957 年  前置型
- COBOL 1959 年    後置型
- Pascal 1970 年代  後置型
- C 言語 1972 年   前置型
- Ada 1980 年代    後置型
- Java 1995 年     前置型

## 仮説

a. 前置型指定のほうがコンパイル時のメモリ使用量が少ない。このため、前置型が主流であり、メモリが増えるときに後置型が発生した
b. 前置型指定は慣習であり、メモリ使用量には影響しない。C 言語前後で前置型が作られたのは偶然である

a/b を検証する。

## 検証方法

1. 前置型指定言語と後置型指定言語で、同じアルゴリズムを実装する
2. コンパイル時のメモリ使用量を測定する

比較対象は、

- 構文木のメモリ使用量
- シンボルテーブルのメモリ使用量

利用メモリは、32KB 以下とする。

## 実装

- 字句解析の実装
- 構文解析の実装
- シンボルテーブルのメモリ容量を比較
- 構文解析時の構文木のメモリ容量を比較

## 文法

以下の文法を解析できるコードを作成する。

### C 言語(前置型指定)

```C
type_specifier: "int" | "float" | "char" ;
declaration: type_specifier identifier ";" ;
```

```C
int x, y;
int sum ;

x = 10 ;
y = 20 ;
sum = x + y ;

int func(int a, int b) {
    return a + b ;
}
```

### Pascal(後置型指定)

```pascal
type_specifier: "Integer" | "Real" | "Char" ;
declaration: "var" identifier ":" type_specifier ";" ;
```

```pascal
var x, y: Integer;
var sum: Integer;
x := 10 ;
y := 20 ;
sum := x + y ;

function func(a: Integer; b: Integer): Integer;
begin
    return a + b ;
end;
```

### Fortran(前置型指定)

```fortran
type_specifier: "INTEGER" | "REAL" | "CHARACTER" ;
declaration: type_specifier identifier_list ";" ;
```

```fortran
INTEGER x, y
INTEGER sum
x = 10
y = 20
sum = x + y

INTEGER FUNCTION func(a, b)
    INTEGER a, b
    func = a + b
END FUNCTION func
```
### COBOL(後置型指定)

```cobol
type_specifier: "PIC 9" | "PIC X" ;
declaration: "01" identifier "PIC" type_specifier "." ;
```

```cobol
IDENTIFICATION DIVISION.
PROGRAM-ID. SAMPLE.

DATA DIVISION.
WORKING-STORAGE SECTION.
01 X        PIC 9(4) VALUE 0.
01 Y        PIC 9(4) VALUE 0.
01 SUM      PIC 9(4) VALUE 0.
01 A        PIC 9(4) VALUE 0.
01 B        PIC 9(4) VALUE 0.
01 RESULT   PIC 9(4) VALUE 0.

PROCEDURE DIVISION.
MAIN-PARA.
    MOVE 10 TO X.
    MOVE 20 TO Y.
    ADD X TO Y GIVING SUM.

    MOVE 5 TO A.
    MOVE 7 TO B.
    PERFORM FUNC-PARA.

    DISPLAY "SUM = " SUM.
    DISPLAY "FUNC(A,B) = " RESULT.

    STOP RUN.

FUNC-PARA.
    ADD A TO B GIVING RESULT.
    EXIT.
```

### Ada(後置型指定)

```ada
type_specifier: "Integer" | "Float" | "Character" ;
declaration: identifier ":" type_specifier ";" ;
```

```ada
X : Integer;
Y : Integer;
SUM : Integer;
X := 10;
Y := 20;
SUM := X + Y;
function Func(A : Integer; B : Integer) return Integer is
begin
    return A + B;
end Func;
``` 

### Java (前置型指定)

```java
type_specifier: "int" | "float" | "char" ;
declaration: type_specifier identifier ";" ;
```

```java
int x, y;
int sum;
x = 10;
y = 20;
sum = x + y;
int func(int a, int b) {
    return a + b;
}
```

### Kotlin(後置型指定)

```kotlin
type_specifier: "Int" | "Float" | "Char" ;
declaration: "var" identifier ":" type_specifier ;
```

```kotlin
var x: Int
var y: Int
var sum: Int
x = 10
y = 20
sum = x + y
fun func(a: Int, b: Int): Int {
    return a + b
}
```

### Lisp(前置型指定)

```lisp
type_specifier: 'int | 'float | 'char ;
declaration: (declaim (type type_specifier identifier)) ;
```

```lisp
(declaim (type 'int x))
(declaim (type 'int y))
(declaim (type 'int sum))
(setf x 10)
(setf y 20)
(setf sum (+ x y))
(defun func (a b)
  (+ a b))
```

Plan モードで作成

vscode + copilot に Plan モードがやって来たので、先の agent.md(つまりは要件定義)から具体的な実装計画を立てて貰います。

実装時に足りなそうなところは、「Further Considerations」に追加されるので、この部分を補足していきます。

## Plan: 前置型/後置型コンパイラメモリ使用量比較ツールの実装

C言語(前置型指定)とPascal(後置型指定)の構文解析器を実装し、コンパイル時のシンボルテーブルとASTのメモリ使用量を比較して、仮説を検証します。現在のワークスペースには実装が存在しないため、すべてゼロから構築する必要があります。

### Steps
1. **トークン定義の実装** - `Token.cs` を作成し、両言語で必要なトークン種別(`int`, `var`, `:=`, `=` など)と `Token` クラスを定義する
2. **字句解析器の実装** - `Lexer.cs` を作成し、C言語用とPascal用の2つのLexerクラスを実装する(共通基底クラス + 派生クラス構成)
3. **ASTノード定義** - `Ast.cs` を作成し、`ProgramNode`, `DeclarationNode`, `FunctionNode`, `AssignmentNode`, `BinaryExpressionNode` などのノードクラスを定義する
4. **シンボルテーブルの実装** - `SymbolTable.cs` を作成し、変数名・型のマッピングとスコープ管理を実装する
5. **パーサーの実装** - `CParser.cs`(前置型)と `PascalParser.cs`(後置型)を作成し、それぞれの文法に基づく構文解析器を実装する
6. **メモリ測定機能の実装** - `MemoryMeasurement.cs` を作成し、GCとオブジェクトサイズを使ったメモリ使用量計測ロジックを実装し、`Program.cs` で比較実行と結果出力を行う

### Further Considerations
1. **メモリ測定手法の選択** - `GC.GetTotalMemory()` を使うか、各オブジェクトのサイズを手動計算するか? → 正確性を重視するなら手動計算を推奨

構文木のノード数、シンボル数のカウントを行い、メモリを計算する。
C# のメモリではなく、計測のために、構文木のノードとシンボルの容量を allocate するように実装する。

できるだけ小さなメモリ(32KB 以下)で動作するように注意する。


2. **Lexer の共通化レベル** - 完全に別クラスにするか、共通基底クラスを使うか? → 比較の公平性のため、できるだけ共通化を推奨

他の文法も試したいので、できるだけ共通化して。
完全に共通化できなくても、構文木の作成とシンボルテーブルの部分は共通化する。

3. **テストデータの規模** - agent.md のサンプルコードだけで十分か、より大きなテストケースを用意するか? → 32KB制約内で複数パターンのテストを推奨

テスト用の文法は、いくつかのパターンを用意して、32KB 以下を想定して実験する。


### メモリ計算の定義
- ASTノード: 基底部分 8byte + 子ノード参照 4byte × 子数
- シンボルエントリ: 識別子名(固定長 16byte)+ 型情報 2byte + スコープ情報 2byte = 20byte
- 測定対象: パース完了時点での構文木 + シンボルテーブルの合計サイズ、およびパース中の最大使用量

### allocate シミュレーション
- `MemoryAllocator` クラスを作成し、32KB のバッファを管理(ノードのカウント数、シンボル数から計算する)
- ノード/シンボル生成時に `Allocate(size)` を呼び出し、上限を越えたら警告を出す。実行は続行してよい。

比較するところはコンパイルするときのメモリ消費量なので、構文木のノードとシンボルテーブルの量を疑似的に計算します。コンパイラによっては、構文解析の部分が言語ごとに異なると思うので、それは別途計算しないといけないかもしれません。今回は、それはパスして、単にノードの数だけを概算しています。

プログラムを作る

vscode + copilot + Claude Opus 4.5 で作っています。最近 Codex が入ったらしいので、そっちを使ったほうが早いかもしれません。

生成コードは C# にしてあります。コード直すことはないのですが、私の場合は C# が直しやすいのはこれで。生成コードは Python にしても構いません。

ファイル名を見るとわかりますが、典型的なコンパイラのファイル名で返してくれます。プログラム言語ごとに Parser が違うだけで、そのほかは共通になっています。Token.cs が各言語でごちゃまぜになっていますが、研究ツールとしてはこれで十分です。

結果

PS H:\ai\check-mem-parser> dotnet run
======================================================================
前置型 vs 後置型 コンパイル時メモリ使用量比較
8言語比較: C, Fortran, Java, Lisp (前置型) vs Pascal, COBOL, Ada, Kotlin (後置型)
======================================================================

=== C言語(前置型) パース結果 ===
AST メモリ: 260 bytes
シンボルテーブル メモリ: 80 bytes
合計メモリ: 340 bytes (0.33 KB)
ノード数: 19
シンボル数: 6
パース中最大メモリ: 632 bytes
エラー数: 0
警告数: 0

=== Fortran(前置型) パース結果 ===
AST メモリ: 284 bytes
シンボルテーブル メモリ: 80 bytes
合計メモリ: 364 bytes (0.36 KB)
ノード数: 20
シンボル数: 6
パース中最大メモリ: 732 bytes
エラー数: 0
警告数: 0

=== Java(前置型) パース結果 ===
AST メモリ: 260 bytes
シンボルテーブル メモリ: 80 bytes
合計メモリ: 340 bytes (0.33 KB)
ノード数: 19
シンボル数: 6
パース中最大メモリ: 632 bytes
エラー数: 0
警告数: 0

=== Lisp(前置型) パース結果 ===
AST メモリ: 280 bytes
シンボルテーブル メモリ: 80 bytes
合計メモリ: 360 bytes (0.35 KB)
ノード数: 20
シンボル数: 6
パース中最大メモリ: 532 bytes
エラー数: 0
警告数: 0

=== Pascal(後置型) パース結果 ===
AST メモリ: 260 bytes
シンボルテーブル メモリ: 60 bytes
合計メモリ: 320 bytes (0.31 KB)
ノード数: 19
シンボル数: 6
パース中最大メモリ: 632 bytes
エラー数: 0
警告数: 0

=== COBOL(後置型) パース結果 ===
AST メモリ: 496 bytes
シンボルテーブル メモリ: 140 bytes
合計メモリ: 636 bytes (0.62 KB)
ノード数: 30
シンボル数: 8
パース中最大メモリ: 724 bytes
エラー数: 0
警告数: 0

=== Ada(後置型) パース結果 ===
AST メモリ: 280 bytes
シンボルテーブル メモリ: 60 bytes
合計メモリ: 340 bytes (0.33 KB)
ノード数: 20
シンボル数: 6
パース中最大メモリ: 648 bytes
エラー数: 0
警告数: 0

=== Kotlin(後置型) パース結果 ===
AST メモリ: 280 bytes
シンボルテーブル メモリ: 60 bytes
合計メモリ: 340 bytes (0.33 KB)
ノード数: 20
シンボル数: 6
パース中最大メモリ: 648 bytes
エラー数: 0
警告数: 0

========================================================================================================================
8言語比較結果
========================================================================================================================

項目                           C言語     Fortran        Java        Lisp      Pascal       COBOL         Ada      Kotlin
------------------------------------------------------------------------------------------------------------------------
AST (bytes)                  260         284         260         280         260         496         280         280
Symbol (bytes)                80          80          80          80          60         140          60          60
合計 (bytes)                   340         364         340         360         320         636         340         340
最大 (bytes)                   632         732         632         532         632         724         648         648
ノード数                          19          20          19          20          19          30          20          20
シンボル数                          6           6           6           6           6           8           6           6

======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================

前置型(C, Fortran, Java, Lisp)平均パース中最大メモリ: 632.0 bytes
後置型(Pascal, COBOL, Ada, Kotlin)平均パース中最大メモリ: 663.0 bytes

結果: 後置型は前置型より平均 31.0 bytes 多くメモリを使用
→ 仮説 a を支持: 前置型の方がメモリ効率が良い可能性
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================

前置型(C, Fortran, Java, Lisp)平均パース中最大メモリ: 632.0 bytes
後置型(Pascal, COBOL, Ada, Kotlin)平均パース中最大メモリ: 663.0 bytes

======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================

前置型(C, Fortran, Java, Lisp)平均パース中最大メモリ: 632.0 bytes
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================

======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================
======================================================================
======================================================================
仮説の検証(前置型 vs 後置型 の平均比較)
======================================================================

前置型(C, Fortran, Java, Lisp)平均パース中最大メモリ: 632.0 bytes
後置型(Pascal, COBOL, Ada, Kotlin)平均パース中最大メモリ: 663.0 bytes

結果: 後置型は前置型より平均 31.0 bytes 多くメモリを使用
→ 仮説 a を支持: 前置型の方がメモリ効率が良い可能性

注: 後置型では識別子を一時的にバッファリングする必要がありますが、
    このサンプルコードの規模では顕著な差は見られません。
    より大規模なコード(多数の変数宣言)で差が出る可能性があります。
PS H:\ai\check-mem-parser>

結果は、前置型と後置型で平均を出して比較しているので、これはあまり意味がありません(AI エージェントが適当に提案したものなので)。個々のノード量を見るとわかりますが、さほど差がありません。COBOL だけ AST が多いのは、これだけ DISPLAY を呼び出して画面表示の関数を呼び出しているからですね。基本的にこれぐらいだと差が出ないはずだし、おそらく構文木のノードにしてもほとんど変わらない筈です。もうちょっと突飛な文法を付けると変化するとは思うのですが、今回は変数定義の前置/後置の違いを検証したかったので、実際にコンパイラを作ってもメモリ容量はさほど変わらない、という結果になります。

ここは発展として、

  • C言語のポインタ「*」の表現を各言語にいれたらどうなるのか?
  • 構造体やクラスの場合は違いがでるのか?
  • ひょっとしたら Lisp とか極端にノードが減ったりしないか?
  • Lua とかスクリプト言語はどうなるのか?

参考コード

https://github.com/moonmile/check-mem-parser

考察 2025/12/06

理解しやすいプログラム言語を考えるうえで、何を基準にするかというと、工学的にはなんらかの計測可能な測定基準が必要になると思うのです。もちろん、進化論的に偶発的に発生する問題(右利き、左利きとか)でもありますが、これを「変数の前置/後置」にして再考したのが上記の結果というわけです。

わたしの経験上、C言語、Fortran 時代が長いので後置型を長く使っているのと「COBOL が前置だった」という勘違いが大きいので、なんらかのメモリの制約があって前置ではなかったか、という仮説を持っていたわけですが…先に書いた通り Ada、COBOL という古い言語に関しても後置となっているので、それは否定できますね。

じゃあ、もう少し視野を広くして「人間にとって理解しやすいプログラム言語の文法とは何か?」を考えるときに、短期記憶の負荷が少ないプログラム言語が理解しやすいのではないか?という仮定に切り替えてみます。つまり、構文解析をするときに探索木が少なくて済むとか、一時的なシンボルのメモリが少なくてすむとか、という明確な測定基準を設定することができます。先の実験プログラムで言えば、ノード数とかシンボル数とかを比較します。そこで、比較したいのは一時的なメモリの最大値です。これは、人間で言えば短期記憶になります。

C 言語の書き方でよく言われるように、一時的な変数は近くに書くとか、スコープを制限して書くとかいう方法があります。関数を短めにするのもそれです。それを数値化するのが短期記憶容量であって一時的なシンボルや構文木ノードの数になります。

また、Rust や関数型プログラムにあるように、束縛変数(変更できない変数)も短期記憶の削減に寄与します。これは、変更されない安全性の確保にもつながりますが、同時に理解しやすいプログラム言語の測定やプログラムコードの書き方に応用できるかも、ってわけです。プログラムコードの複雑さについては密結合に関してや条件文のからみあいを計測する手法(サイクロマティック複雑度 https://emb-sw-eng.com/cyclomatic_complexity/) が既にあるのですが、もうちょっと違う視点でできるかなと。あと、プログラム言語の文法の設計あるいは言語拡張にも応用できるかもしれません。

カテゴリー: 開発 | プログラム言語の型が何故前置が多かったのか? はコメントを受け付けていません

国際ロボット展 2025 の感想文(主にロボットアーム系)

開催概要 | 2025国際ロボット展 https://irex.nikkan.co.jp/overview/ に行ってきたので、この感想文です。

みなさん、歩く方のロボットが目当てっぽいのですが、私の場合ロボットアームが目当てなのでちょっと視点がずれています…というか異なります。この手の産業展は写真不可が多いのですが、ロボット展は可なんですね。なので、写真付きです。
ちなみに、面倒だったのでセミナーのほうは行ってません。歩くロボットの展示はセミナーが多かったみたいなので、何らかの予約をしていくと良いです。

最初の探したのが川崎重工です。四足のコンセプトロボットではなくて、以前展示されていた ROS で動作している歩行ロボット(確かピックアップ利用目的)の開発がどうなったのかな、というところです。

どうやら、実演を見逃してしまったようで「Kaleido(カレイド)」という形で開発されています。中味のが ROS2 かどうかは分かりません。

「踊らない」ヒト型ロボで逆転狙う日本勢 川崎重工、最新版を公開 – 日本経済新聞 https://www.nikkei.com/article/DGXZQOUC02BVH0S5A201C2000000/

結構注目を集めていたのが、NACHi https://www.nachi-fujikoshi.co.jp/ の産業ロボットです。会場でも相当数 NACHi バッグを持っている人がいます。

これが凄いのは、複数のロボットアームが協調動作をしているのですが、相互にぶつからないように作業ができています。これ、自動車の塗装や溶接だと事前プログラムで衝突を回避するのですが、不二越ロボットはリアルタイム(MZS OS だったか)で相互干渉を避けることができます。これは木製の線路かたずけを行っているデモですが、他にもボールやビスの片づけとか4,5台のロボットアームが協調動作するところが見られます。

「皆さんご存じデジタルツイン」の展示です。仮想空間にロボットアームや機器を配置させて、競合を避ける手法のひとつです。従来だと、実際の機器で動作範囲を指定していたのですが、現在はこのような仮想空間を利用します。という形で「デジタルツイン」です。

実際にはロボットアーム自体の物理量(振り回しやトルクとか)があるので、その許容範囲を計算するのと、作業台の上の道具の位置が若干ことなるとか、そういう差異を吸収しないといけません。そのあたりは、具体的にロボットアームの動作プログラムをしている方がぽろぽろと X のタイムラインにもいるので探してください。企業秘密なのでなかなか見つかりませんが、趣味ロボットを作っている方を漁ると出会えたりします。つまりは、仕事でもロボットだし、趣味でもロボットなわけです。

ロボット展なので、いわゆる「ロボット」が多いのですが、こういうのもロボットです。商品のピックアップを画像認識とローラーで判別して送り出すことができます。

Amazon のように多種多様なピックアップの場合、棚移動ロボットとロボットのような人間によるピックアップの組み合わせがよいのですが、この展示のようにある程度かたちが揃っている場合はベルトコンベア方式でピックアップすると高速にできます。たぶん、ローラーで商品判別をするパターンの応用だと思うのですが、見どころは手間のローラー部分ではなくて、裏のほうで動いているボールがたくさんついてるローラーのほうです。可動部分が多くて連続動作はどうなのだろう? と思ったりもしますが、かなりスムーズに動けます。

ソフトウェアとして Unity が使われています、の例です。HMD 対応のソフトウェア開発もそうなのですが、画像関係では Unity が欠かせません。Unity 自体の利用はゲーム業界が多いとは思うのですが、こういうリアルタイム動作の UI によく使われています。

同じ画像関係として Unreal Engine があるのですが、この手のロボットや画像研究展では Unity が使われていることが多いです。多分 C# と C++ の違いかなと思います。Unreal Engine で C++ を使ってがりがりやって、他との通信部分を作るのは辛いでしょう。そのあたりは、C# で豊富なライブラリを使った方が有利、ということです。Unity 自体のブースもロボット展に出ています。

もう、見飽きてしまった位の7軸ロボットですが、ピックアップ&協調動作と言う形で広く使われています。

この丸みをおびたデザインは、実はドイツの Industry 4.0 が発祥になります。Industry 4.0 は工場の自動化と同時に、ロボットが人と協調するときの安全性も進めていました。

インダストリー4.0とは?日本の製造業への応用やドイツの狙いをわかりやすく解説! | NTTドコモビジネス 法人のお客さま https://www.ntt.com/business/services/xmanaged/lp/column/industry-4-0.html

IoT とか工場のセンサーとか、自動発注システムとかも Industry 4.0 の一部ですね。それ以前は無骨で指や腕を持って行かれそうなデザインだったわけですが、このような形で丸みを帯びたカーブになっています。これだと指を挟むことがありません。ちょうど、歯医者の支持アームのようなものです。

もちろん、完全無人化の場合は、人間への配慮が必要がないのでこのての形状じゃないものも多いです。自動車の溶接や塗装アームとは異なる分野になります。

ちなみに写真を取り忘れてしまったのですが、ロボットアームの作業を「共創」するパターンがあります。先の MZS OS の場合は一社だけの協調作業ですが、「共創」する場合は、多様な会社のロボットアームを協調動作させることが目的になります。いろいろな会社が、その会社特有のアームを持ち出して、協調的に作業をできるというスタイルです。今回でも、このスタイルを主張する会社が2社ほどありました。

他社のロボットアームが AI などを使った自動化に重きを置くのですが、人力の操作に重きを置いたのが「人機一体」です。いわゆる従来型の人が操作するロボットアームになりますが、重機や臨機応変な作業にはこれが有効になります。

おそらく、一番の売りは「フィードバックが来るようになった」です。HMD で遠隔操作ができると同時に、アームが何かに触ったときに、人間の操作アームのほうにフィードバックが入ります。これができないと、押したり引いたりの力加減を「視界」だけで行なわなければいけません。いわゆる触覚です。これは遠隔地の電板を開いてブレーカーを操作しているところです。これで高所や高圧線に近いところでも人間は安全に作業ができるということです。まあ、ロボットも結構な値段がするでしょうが、人間の命のほうが高いですからね。場合によってはロボットが犠牲になるのです。

ちなみに、人機一体の缶バッチが売ってます。この手の展覧会でノベルティグッズを売っているのは初めて見ました。買っておけばよかった。ちょっとスルーしてしまいました。

ワイヤー型のロボットアームがありました。このワイヤー型は7軸ロボットに押されてしまっていますが、実は非常に高速に動きます。

これ、ワイヤーの自重がロボットアーム型よりも軽いので、凄く素早く動くんですよね。慣性力が少ないのです。しかも、巻き取りのステッピングモーターだけで済ませられるというのが特徴です。おそらく、重さ制限はあるのですが、こんな形で液体ボトルのぴくアップができます。他にも、この展覧会では見なかったですが、食品業界で使われる三軸のアームがあります。これも凄い高速で動くのでお薦めです…何に薦めるかわかりませんが。

この手のロボット展ですが、以前は物体認識の画像研究のシンポジウムと組み合わせるのが必要だったのですが、いまだと普通に組み込まれています。画像認識(キーエンスの薬品識別とか)は別な分野でもあるのですが、ロボットアームについても必ず組み込まれるようになっています。特に、人との協調ロボットには必須の機能ですよね。

そんな訳で、金曜日、土曜日とまだ展示会があるので、都合をつけてビックサイトに行ってみるのもよいです。

カテゴリー: 開発 | 国際ロボット展 2025 の感想文(主にロボットアーム系) はコメントを受け付けていません

Siv3D でもバイブコーディング!!!

本記事は Siv3D – Qiita Advent Calendar 2025 – Qiita https://qiita.com/advent-calendar/2025/siv3d の 3日目の記事です。

ChatGPTが一般的に使われ始めて2年ほど経ち、今度は AI エージェントというのが今年の4月頃から流行りはじめました。ChatGPT を使って、プログラミングのコードを教えて教えて貰うのはいいのですが、いちいちコードをコピペしないとイケなかったり、そもそも大量のコードをを ChatGPT に貼り付けないと文脈をうまく読み取ってくれないという問題がありました。が、これを Visual Studio Code 上なので直接ファイルを弄ってくれるのが AI エージェントの役割です。ちょっと危険な気もする(まあ、実際のところ MCP あたりが暴走すると危険なんですが)けれども、うまく使いこなせればこその「道具」なので、その道具を使いこなしていきましょう、というのがこの記事の主旨です。

バイブコーディング(vibe coding)とは?

ChatGPT にちまちまと関数のコードを貼り付けていたのととは違って、生成 AI を使って一気にコードを作成してしまおうというのが「バイブコーディング」の主な特徴です。いわゆる、AI にコードを書かせてしまって、コードを書く楽しみが…とも言えるのですが、職業プログラマとしては、

  • テストツールなどのたいくつなツールは AI に書かせた方が早い
  • 使い捨てツール=治具などは、AI に書かせたほうが早い
  • 最初のひな形は、AI にやってもらってたたき台にすることができる

という形で、使い捨てであったり最初のアイデアを実現するツールにバイブコーディングを使うのが一番効果的です。その後で、実際に仕事に使う場合にはコードの検証やら正確性やらが必要になってくるので、AI コードそのままを使うことはできませんが、それでも私自身の場合は、

  • 複雑怪奇になってしまったコードの解析を AI に頼む
  • 長々と出てくるエラーコードの解析を AI に頼む
  • ちょっと古めのコードを AI に読んで貰って、修正点のアイデアを出して貰う

のようなことをやります。最近は AI 任せにするツールや手法が盛んではありますが、その一歩手前の「AIペアプロ」がお勧めです。まあ、慣れてきたら、設計書を書くなりして一晩ぐるぐる回すほうが効果的ではあるのですが、慣れないうちはちまちま設計用の Readme.md や Agents.md(ファイル名は実は何でも構いません)に書いておいて、AI にちょっと作って貰うというのを繰り返すのがよいでしょう。自分なりのコーディングスタイルを掴んでください。

C++ でバイブコーディングできるのか?

巷に公開されているものは React などの Node.js や TypeScript を使った Web 開発のものが多いでしょう。設計書ノウハウ(「仕様書駆動」や「sepc駆動」というやつです)も、そのあたりのものが多いです。Web 開発の場合、コードを修正した途端に UI が変更になるとか、npm を使うとかするパターンが多いので、果たして C++ のように間にコンパイルが挟む場合はどうなるのか? という不安はありますが、大丈夫です。C++ でも十分使えます。しかも、Siv3D のような独自なライブラリを使っている場合でも、最近の生成 AI はうまく扱ってくれます。おそらく、Siv3D のチュートリアルサイト https://siv3d.github.io/ja-jp/ があるのが大きいと思います。つまり、Web サイトにある情報をうまく学習しているのが、現在の AI エージェントのモデルになっているわけです。

また、AI エージェントによるプログラム言語のコンバートの能力も上がっています。ロジックに関しては、一般的なプログラム言語はかなり網羅されています。別件ではありますが、Python のコードを VB.NET のコードに変換することもできます。あるいは、Kotlin や Swift のコードに変換したりします。それぞれデータベースなどライブラリの使い方は異なるのですが、それもプログラム言語特有のもの、かつ、そのプログラム言語で使われているライブラリをうまく使って変換してくれます。

Siv3Dでバイブコーディングしてみる

まあ、ここまで長々と書いてようやく Siv3D のコーディングに辿り着くわけですが、あまりやることがないので、ちょっと長々と書いてしまっているだけです。

AI エージェントでは、Visual Studio Code + Claude Sonnet を使っています。GitHub Copilot ならば月10ドルで契約が可能です。学生ならば 0 円で利用ができます。他にも結構高めなものもありますが、夜中にぐるぐる回さない限りこれで十分です。Google の Gemini のほうは分からないのですが、どの AI エージェントを使ってもだいたい似た感じでいけます。好きなものを使ってください。

AI エージェントによっては、プロジェクトを自動作成するところからできるパターンも多いのですが、このように Visual Studio を使ってプロジェクトテンプレートを使ってひな形のプロジェクトだけ作っておくとよいです。ビルドも自動化できるのですが、結構面倒なので、AI ペアプロとしてビルドは人間のほうの担当にします。

仕事としてはファイルを分けたほうがいいのですが、今回は面倒なので Main.cpp に全てを突っ込みます。この初期状態でビルドができるところまで確認しておきます。

agents.md を作成する

実はファイル名は Agents.md でも Readme.md でも Claudes.md でもなんでも構いません。夜中に自動化しようとすると、ファイル名を決めたほうがうまく AI エージェントに伝わるのですが、AI ペアプロの場合には「agents.md に従ってコードを作成して」とプロンプトで伝えるだけで十分です。ファイル名を頼りにうまく探してくれます。

agents.md の中身は以下のようになっています。

# ウマ娘レース

## 概要

ウマ娘レースは、ウマ娘たちが競い合うレースイベントです。
プレイヤーは、ウマ娘のパラメータを調節してトップを目指します。

## ルール

- レースは、横一列に並んだ5人のウマ娘で行なわれる
- 各ウマ娘には、スピード、スタミナ、パワー、根性、賢さの5つのパラメータがある
- プレイヤーは、自分のウマ娘のパラメータを調整してレースに挑む
- レースは複数のターンで構成され、各ターンでウマ娘たちは前進する
- 最終的にゴールに最も早く到達したウマ娘が勝者となる


## パラメータの説明

- スピード: ウマ娘の基本的な速さを決定するパラメータ
- スタミナ: ウマ娘が長距離を走る際の持久力を決定するパラメータ
- パワー: ウマ娘の加速力や坂道での強さを決定するパラメータ
- 根性: ウマ娘が苦しい状況でも踏ん張る力を決定するパラメータ
- 賢さ: ウマ娘がレース中に適切な判断を下す能力を決定するパラメータ

パラメータは、合計 20 ポイントまで振り分けることができる。


## レースの進行

- 各ターンでウマ娘たちは、パラメータに基づいて前進距離が決定される

サイコロ 1から6までのサイコロを2個振る。

前進距離 =
スピード * サイコロA +
スタミナ * サイコロB +
パワー * (サイコロA + サイコロB) / 2 +
根性 * ランダム値(3から6) +
賢さ * ランダム値(1から3)

各ウマ娘は順番サイコロを振り、出た目の大きい順に前進距離を計算していく。
最初に 1000 メートルに到達したウマ娘が勝者となる。

## 画面構成

- レーストラック: ウマ娘たちが走るコースが表示される。横長の一列でよい。
- 自分のウマ娘のパラメータを入れる
- 他のウマ娘のパラメータはランダムに設定される
- スタートボタンを押すと、レースが開始される。1秒毎に順番サイコロを振る。

## 設計

// ウマ娘の構造体
struct UmaMusume {
name: String,
speed: u8,
stamina: u8,
power: u8,
guts: u8,
wisdom: u8,
position: u32,
};

// レースの構造体
struct Race {
uma_musumes: Vec<UmaMusume>,
track_length: u32
};

これも慣れるとここまで書けるのですが、慣れないうちはそんなに細かく書かなくてもいいです。はっきり言って、概要とルールだけ書いても、このウマ娘レースは作成されてしまいます。どうやら、この手のゲームロジック(ブロック崩しとかインベーダーゲームとか)は生成 AI の学習データにあるっぽくて、それらしい何処かで見たようなものをうまく出してくれます。まあ、端的に言えばパクりなのですが、ここでは目をつぶっておきましょう。私的範囲内の利用ということです。

AI にコーディングしてもらう。

プロンプトで「agents.md に従ってコーディングして」と頼むだけです。AI エージェントのほうもだんだん賢くなっているので、結構なスピードでコードを生成してくれます。

見てわかるのですが、

  • パラメータを入れるためのセットアップ画面
  • レース中の画面
  • 結果を表示する画面

の3つに分かれています。この要素は agents.md に入れていないので、つまりはこれはどこからからパクって来たものです。こんな風に典型的な画面の作りの場合には AI エージェントが一番効率的に動きます。逆に、この3つの画面を、うまくひとつの画面に落とし込むのはなかなか苦労します。独自画面は苦手なんですよね。

そのような場合は、あらかじめコードのほうに、1画面の配置をコーディングしてしまうとうまくいきます。agents.md にあれこれと画面構成を記述する方法も可能なのですが、AI エージェント自身が既存のコードを解析しながら、そこに付け加えるように進めていくのでコード自身で伝えるほうが効果的です。

逆に、うまくコードで伝わらないときは、agents.md を作り直して一からバイブコーディングをしてしてみてください。バイブコーディングの良いところは、うまくいかなかった時はすべてを捨ててやり直しができるところです。これが人間がコーディングをすると、手間がかかっていたり再利用を考えたりするところなのですが、バイブコーディングの場合はコーディングの作成時間が短い&人間の労力が少ないので、最初からやり直してしまったほうが早いです。同じ agents.md であっても、確率的に別な構造になるので、何度かやってうまくいったものを利用する手があります。

もっとも、数行のプロンプトだけでうまくいくまで AI に書いて貰うという学校の課題方式ではだめです。ちょっとずつ agents.md を書き変えて、うまくパターンをみつけるのが人間の仕事になります。

できあがった Main.cpp

しばらく待つと Main.cpp ができあがります。

# include <Siv3D.hpp> // Siv3D v0.6.16

// ウマ娘の構造体
struct UmaMusume
{
	String name;
	int32 speed;
	int32 stamina;
	int32 power;
	int32 guts;
	int32 wisdom;
	int32 position;
	Color color;

	// パラメータの合計を計算
	int32 totalParams() const
	{
		return speed + stamina + power + guts + wisdom;
	}

	// 前進距離を計算
	int32 calculateAdvance() const
	{
		int32 diceA = Random(1, 6);
		int32 diceB = Random(1, 6);
		int32 gutsRandom = Random(3, 6);
		int32 wisdomRandom = Random(1, 3);

		int32 advance = speed * diceA
			+ stamina * diceB
			+ power * (diceA + diceB) / 2
			+ guts * gutsRandom
			+ wisdom * wisdomRandom;

		return advance;
	}
};

// レースの構造体
struct Race
{
	Array<UmaMusume> umaMusumes;
	int32 trackLength = 1000;
	bool isRunning = false;
	bool isFinished = false;
	int32 winnerIndex = -1;
	Stopwatch turnTimer;

	// ランダムなパラメータを生成(合計20ポイント)
	static UmaMusume createRandomUma(const String& name, const Color& color)
	{
		UmaMusume uma;
		uma.name = name;
		uma.color = color;
		uma.position = 0;

		// ランダムに20ポイントを振り分け
		Array<int32> params = { 1, 1, 1, 1, 1 }; // 最低1ずつ
		int32 remaining = 15; // 残り15ポイント

		for (int32 i = 0; i < remaining; ++i)
		{
			int32 idx = Random(0, 4);
			params[idx]++;
		}

		uma.speed = params[0];
		uma.stamina = params[1];
		uma.power = params[2];
		uma.guts = params[3];
		uma.wisdom = params[4];

		return uma;
	}

	// レースを1ターン進める
	void advanceTurn()
	{
		if (isFinished) return;

		// 各ウマ娘の前進距離を計算
		Array<std::pair<int32, int32>> advances; // (前進距離, インデックス)
		for (size_t i = 0; i < umaMusumes.size(); ++i)
		{
			advances.push_back({ umaMusumes[i].calculateAdvance(), static_cast<int32>(i) });
		}

		// 前進距離の大きい順にソート
		std::sort(advances.begin(), advances.end(), [](const auto& a, const auto& b) {
			return a.first > b.first;
		});

		// 順番に前進
		for (const auto& [advance, idx] : advances)
		{
			umaMusumes[idx].position += advance;

			// ゴール判定
			if (umaMusumes[idx].position >= trackLength && !isFinished)
			{
				isFinished = true;
				winnerIndex = idx;
				isRunning = false;
			}
		}
	}
};

void Main()
{
	// ウィンドウサイズを設定
	Window::Resize(1200, 700);
	Scene::SetBackground(ColorF{ 0.3, 0.6, 0.3 });

	// フォントを作成
	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
	const Font smallFont{ FontMethod::MSDF, 32 };

	// ウマ娘の絵文字
	const Texture umaEmoji{ U"🏇"_emoji };

	// ウマ娘の名前と色
	const Array<String> umaNames = { U"プレイヤー", U"スペシャル", U"サイレンス", U"トウカイ", U"マチカネ" };
	const Array<Color> umaColors = { Palette::Red, Palette::Blue, Palette::Green, Palette::Orange, Palette::Purple };

	// レース
	Race race;

	// プレイヤーのパラメータ
	double playerSpeed = 4;
	double playerStamina = 4;
	double playerPower = 4;
	double playerGuts = 4;
	double playerWisdom = 4;

	// ゲーム状態
	enum class GameState { Setup, Racing, Result };
	GameState state = GameState::Setup;

	while (System::Update())
	{
		// タイトル表示
		font(U"🏇 ウマ娘レース 🏇").drawAt(48, Vec2{ 600, 40 }, Palette::White);

		if (state == GameState::Setup)
		{
			// パラメータ設定画面
			font(U"パラメータを設定してください").drawAt(32, Vec2{ 600, 100 }, Palette::Yellow);

			const int32 sliderX = 400;
			const int32 sliderWidth = 200;
			int32 y = 150;

			// パラメータスライダー
			SimpleGUI::Slider(U"スピード: {:.0f}"_fmt(playerSpeed), playerSpeed, 1.0, 10.0, Vec2{ sliderX, y }, 180, sliderWidth);
			y += 50;
			SimpleGUI::Slider(U"スタミナ: {:.0f}"_fmt(playerStamina), playerStamina, 1.0, 10.0, Vec2{ sliderX, y }, 180, sliderWidth);
			y += 50;
			SimpleGUI::Slider(U"パワー: {:.0f}"_fmt(playerPower), playerPower, 1.0, 10.0, Vec2{ sliderX, y }, 180, sliderWidth);
			y += 50;
			SimpleGUI::Slider(U"根性: {:.0f}"_fmt(playerGuts), playerGuts, 1.0, 10.0, Vec2{ sliderX, y }, 180, sliderWidth);
			y += 50;
			SimpleGUI::Slider(U"賢さ: {:.0f}"_fmt(playerWisdom), playerWisdom, 1.0, 10.0, Vec2{ sliderX, y }, 180, sliderWidth);

			// 合計ポイント表示
			int32 total = static_cast<int32>(playerSpeed) + static_cast<int32>(playerStamina)
				+ static_cast<int32>(playerPower) + static_cast<int32>(playerGuts) + static_cast<int32>(playerWisdom);

			Color totalColor = (total <= 20) ? Palette::Lime : Palette::Red;
			font(U"合計: {} / 20 ポイント"_fmt(total)).drawAt(28, Vec2{ 600, 430 }, totalColor);

			if (total > 20)
			{
				smallFont(U"※ 合計20ポイント以下にしてください").drawAt(24, Vec2{ 600, 470 }, Palette::Red);
			}

			// スタートボタン
			if (SimpleGUI::Button(U"レーススタート!", Vec2{ 500, 520 }, 200, (total <= 20)))
			{
				// レースを初期化(代入ではなくメンバごとに再初期化)
				race.umaMusumes.clear();
				race.trackLength = 1000;
				race.isRunning = false;
				race.isFinished = false;
				race.winnerIndex = -1;
				race.turnTimer.restart(); // タイマーをリセットして再スタート(既存インスタンスを再利用)

				// プレイヤーのウマ娘を作成
				UmaMusume player;
				player.name = umaNames[0];
				player.color = umaColors[0];
				player.speed = static_cast<int32>(playerSpeed);
				player.stamina = static_cast<int32>(playerStamina);
				player.power = static_cast<int32>(playerPower);
				player.guts = static_cast<int32>(playerGuts);
				player.wisdom = static_cast<int32>(playerWisdom);
				player.position = 0;
				race.umaMusumes.push_back(player);

				// 他のウマ娘をランダムに作成
				for (size_t i = 1; i < umaNames.size(); ++i)
				{
					race.umaMusumes.push_back(Race::createRandomUma(umaNames[i], umaColors[i]));
				}

				race.isRunning = true;
				race.turnTimer.restart();
				state = GameState::Racing;
			}

			// パラメータ説明
			y = 580;
			smallFont(U"スピード: 基本的な速さ | スタミナ: 持久力 | パワー: 加速力").draw(18, Vec2{ 200, y }, Palette::White);
			smallFont(U"根性: 踏ん張る力 | 賢さ: 判断力").draw(18, Vec2{ 200, y + 30 }, Palette::White);
		}
		else if (state == GameState::Racing)
		{
			// レース画面
			const int32 trackY = 200;
			const int32 trackHeight = 80;
			const int32 trackStartX = 100;
			const int32 trackWidth = 1000;

			// トラック背景を描画
			for (size_t i = 0; i < race.umaMusumes.size(); ++i)
			{
				int32 laneY = trackY + static_cast<int32>(i) * trackHeight;

				// レーン背景
				Rect{ trackStartX, laneY, trackWidth, trackHeight - 5 }
					.draw(ColorF{ 0.8, 0.6, 0.4 })
					.drawFrame(2, Palette::Brown);

				// ゴールライン
				Rect{ trackStartX + trackWidth - 10, laneY, 10, trackHeight - 5 }.draw(Palette::Red);
			}

			// 1秒ごとにターンを進める
			if (race.isRunning && race.turnTimer.ms() >= 1000)
			{
				race.advanceTurn();
				race.turnTimer.restart();
			}

			// ウマ娘を描画
			for (size_t i = 0; i < race.umaMusumes.size(); ++i)
			{
				const auto& uma = race.umaMusumes[i];
				int32 laneY = trackY + static_cast<int32>(i) * trackHeight;

				// 位置を計算(トラック幅に合わせてスケーリング)
				double progress = static_cast<double>(uma.position) / race.trackLength;
				progress = Min(progress, 1.0);
				int32 drawX = trackStartX + static_cast<int32>(progress * (trackWidth - 50));

				// ウマ娘の絵文字を描画
				umaEmoji.scaled(0.5).drawAt(drawX + 25, laneY + trackHeight / 2 - 2);

				// 名前と位置を表示
				smallFont(uma.name).draw(18, Vec2{ 10, laneY + 20 }, uma.color);
				smallFont(U"{}m"_fmt(uma.position)).draw(16, Vec2{ 10, laneY + 45 }, Palette::White);
			}

			// レース情報
			font(U"ゴール: {}m"_fmt(race.trackLength)).draw(24, Vec2{ 100, 620 }, Palette::White);

			// レース終了判定
			if (race.isFinished)
			{
				state = GameState::Result;
			}
		}
		else if (state == GameState::Result)
		{
			// 結果画面
			font(U"🎉 レース終了! 🎉").drawAt(48, Vec2{ 600, 150 }, Palette::Yellow);

			if (race.winnerIndex >= 0)
			{
				const auto& winner = race.umaMusumes[race.winnerIndex];
				font(U"優勝: {}"_fmt(winner.name)).drawAt(40, Vec2{ 600, 220 }, winner.color);

				if (race.winnerIndex == 0)
				{
					font(U"おめでとうございます!").drawAt(32, Vec2{ 600, 280 }, Palette::Lime);
				}
				else
				{
					font(U"残念...次は勝てるかも!").drawAt(32, Vec2{ 600, 280 }, Palette::Orange);
				}
			}

			// 順位表示
			Array<std::pair<int32, size_t>> ranking;
			for (size_t i = 0; i < race.umaMusumes.size(); ++i)
			{
				ranking.push_back({ race.umaMusumes[i].position, i });
			}
			std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
				return a.first > b.first;
			});

			int32 y = 340;
			smallFont(U"【最終順位】").draw(24, Vec2{ 500, y }, Palette::White);
			y += 40;

			for (size_t rank = 0; rank < ranking.size(); ++rank)
			{
				const auto& uma = race.umaMusumes[ranking[rank].second];
				smallFont(U"{}位: {} ({}m)"_fmt(rank + 1, uma.name, uma.position))
					.draw(22, Vec2{ 480, y }, uma.color);
				y += 35;
			}

			// リトライボタン
			if (SimpleGUI::Button(U"もう一度プレイ", Vec2{ 500, 580 }, 200))
			{
				state = GameState::Setup;
			}
		}
	}
}

実は、生成されるコードはビルドが通るとは限りません。今後の AI エージェントでは改善されると思いますが、文法的にちょっと間違っていたり、関数の整合性が合わないところがちょこちょこ出てきます。

このコードでは既に修正してありますが、スタートボタンを押したときの「// レースを初期化(代入ではなくメンバごとに再初期化)」の部分で、Race 構造体の初期化でコンパイルエラーがでていました。

race = Race{} ;

Race 構造体にデフォルトコンストラクタがないので、これでは駄目です。

race.umaMusumes.clear();
race.trackLength = 1000;
race.isRunning = false;
race.isFinished = false;
race.winnerIndex = -1;
race.turnTimer.restart(); // タイマーをリセットして再スタート(既存インスタンスを再利用)

まあ、他にも修正点はあるかもしれませんが、ひとまずビルドして動かしてみましょう。

動作確認

動かすと、馬が後ろ向きに走ってしまっているとか、パラメータを保存できないとか、ケース結果が蓄積されないとか、いろいろ改善点があります。

これは agents.md に書き込んで「この部分を修正して」とプロンプトに入れてもよいし、自分でコードを書き直してもよいです。コードのコメントに修正案を書いて、「この部分を実装して」でも AI がコードを作ってくれます。

では、AI ペアプロで、よい Siv3D プログラミングライフを!

サンプルコード

moonmile/siv3d_uma_race https://github.com/moonmile/siv3d_uma_race

カテゴリー: 開発 | Siv3D でもバイブコーディング!!! はコメントを受け付けていません

エコーチェンばーちゃんの開発

7月頃だったか、新人研修が終わって AI エージェントがちょっと流行り始めた頃に1か月ほどあれこれと弄ってい時期があります。その頃は Claude Sonnet 4 を使って試していたのですが、いまだと Claude Opus 4.5 が使えるようになったので、もうちょっとマシになっているかなと思ってのお試しです。

この手のブラウザを使ったなんらかの Web サービスみたいなのは React で作っておくと楽です。私自身は React は得意じゃないのですが、AI エージェントが得意ならばそれでいいです。AI エージェントの得意分野のほうにあわせましょう、というスタイルですね。

これも、あらかじめ、React プロジェクトを作成しておきます。AI エージェントでプロジェクトから作成して貰ってもいいのですが、初期状態はバージョン関係か初期値のフォルダーの問題とかがあるので、手作業でやります。

npx create-react-app echo-cham-baachan

で、プロジェクトを作成しておいて、ここからスタートです。

Readme.md を作る

なかみは夏ごろに作ったものに少し手を入れています。書き方は機能から作ってもいいし、画面構成から考えてもいいです。本当は、いろいろと目的からスタートすればいいのですが、基本的に「エコーチェンバー現象」のシミュレーションツールというネタ機能なので、これだけで十分です。

# エコーチェンばぁちゃん

小中学生向けに、自分の意見が通る「エコーチェンバー現象」を疑似体験するツール。
反対意見がでてこないので、自分の意見が正しいと錯覚しやすくなる。

エコーチェンバー現象 - Wikipedia 
https://ja.wikipedia.org/wiki/%E3%82%A8%E3%82%B3%E3%83%BC%E3%83%81%E3%82%A7%E3%83%B3%E3%83%90%E3%83%BC%E7%8F%BE%E8%B1%A1

## 機能

- 自分の意見を入れると、5人のエコーチェンばぁちゃんが賛同してくれる。
- ばぁちゃんたちは、賛同の言葉をランダムに変化させる。
- 画面は、LINE のように吹き出しとばぁちゃんのアイコンが表示される。
- ばぁちゃんの性格は、いくつかのシステムプロンプトを用意して切り替えられる。

## 画面構成

- 入力エリア
  - 自分の意見を入力するテキストエリア
  - 送信ボタン
- チャットエリア
  - ばぁちゃんのアイコンと吹き出しで構成されるチャット表示エリア

## 技術スタック

- フロントエンド: React
- フロントエンドから OpenAI API へのリクエスト: Axios
- デプロイ: Vercel
- AI モデル: OpenAI GPT-5

## 環境変数 .env

- OPENAI_API_KEY: OpenAI API キー

Plan.prompt.md を作る

開発プランを vscode + copilot の Plan モードを使って作って貰います。

## Plan: エコーチェンばぁちゃん アプリ実装

README.mdに基づき、小中学生向けのエコーチェンバー現象疑似体験ツールを実装する計画です。Create React Appの初期状態から、LINEライクなチャットUIと5人のばぁちゃんAIを構築します。

### Steps

1. **依存関係のインストール** - `axios`を追加し、`package.json`を更新
2. **環境変数の設定** - `.env`ファイルを作成し、`REACT_APP_OPENAI_API_KEY`を定義
3. **UIコンポーネントの作成** - `src/components/`配下に`ChatContainer`、`MessageBubble`、`GrandmaAvatar`、`InputArea`を実装
4. **ばぁちゃんの設定ファイル作成** - `src/data/grandmas.js`に5人のばぁちゃん情報(名前、アイコン、システムプロンプト)を定義
5. **OpenAI APIサービスの実装** - `src/services/openai.js`でAPIリクエスト処理を作成
6. **メインApp.jsの実装** - `src/App.js`でチャット状態管理とコンポーネント統合
7. **LINEライクなスタイリング** - `src/App.css`で吹き出し・チャットUIのCSSを実装

### Further Considerations

1. **APIキーのセキュリティ** - フロントエンドから直接APIを呼ぶとキーが露出します。Vercel Serverless Functions (`/api/chat.js`) でラップする方式に変更しますか?

Vercel Serverless Functions を使う方式で。

2. **ばぁちゃんアイコン** - 5人分のアイコン画像はどこから用意しますか?(絵文字/生成AI/フリー素材)

ひとまず、絵文字で。

3. **性格の切り替え機能** - READMEに「システムプロンプトを用意して切り替えられる」とありますが、UIでの切り替え方法は?(ドロップダウン/タブ/設定画面)

設定が画面を作って。

足りない設定は「Further Considerations」に書かれているので、これを付けたします。いきなり Agent モードで作成すると、ばぁちゃんアイコンとか設定を想定で作ってしまうので、後からの修正が結構面倒です。なので、あらかじめ Plan を出して貰ってから、その後で Agent で進めるのがコツですね。

開発する

開発の頼み方はいろいろあるのですが、要は AI に通じればいいです。今回の場合は 30 分位で完了です。途中、いくつかフォルダーを作ったりするときに承認ボタンがでてきますが、これを押せば ok です。

実行する

まあ、質問をすると、なんでも肯定的に返してくれるので、こんな感じになります。

内部で使われているシステムプロンプトがこんな感じ。このシステムプロンプトも AI エージェント自身が作っているので、私は手をいれていません。他の性格を作りたいときは、この部分を直接書き変えてしまえばよいです。

// 性格タイプ別のシステムプロンプト
export const systemPrompts = {
  supportive: [
    `あなたは「ウメばぁちゃん」という優しいおばあちゃんです。
相手の意見には必ず賛同し、応援してください。
「そうそう」「その通りじゃ」「よく言った」などの言葉を使ってください。
返答は2〜3文程度で、温かみのある口調で話してください。
語尾は「〜じゃよ」「〜じゃね」「〜じゃのう」を使ってください。`,

    `あなたは「ハナばぁちゃん」という物知りなおばあちゃんです。
相手の意見には必ず賛同し、「さすがじゃ」「賢いのう」と褒めてください。
返答は2〜3文程度で、穏やかな口調で話してください。
語尾は「〜じゃよ」「〜じゃね」「〜じゃのう」を使ってください。`,

    `あなたは「トメばぁちゃん」という元気なおばあちゃんです。
相手の意見には必ず賛同し、「いいねぇ」「最高じゃ」と盛り上げてください。
返答は2〜3文程度で、明るい口調で話してください。
語尾は「〜じゃよ」「〜じゃね」「〜じゃのう」を使ってください。`,

    `あなたは「キヨばぁちゃん」という優しいおばあちゃんです。
相手の意見には必ず賛同し、「よく分かるよ」「うんうん」と共感してください。
返答は2〜3文程度で、包み込むような口調で話してください。
語尾は「〜じゃよ」「〜じゃね」「〜じゃのう」を使ってください。`,

    `あなたは「フミばぁちゃん」という知的なおばあちゃんです。
相手の意見には必ず賛同し、「なるほど」「深いのう」と感心してください。
返答は2〜3文程度で、落ち着いた口調で話してください。
語尾は「〜じゃよ」「〜じゃね」「〜じゃのう」を使ってください。`,
  ],

ChatGPT との会話でエコーチェンバー化するときは1対1なので、末尾のおべっかあたりを無視すればいいのですが、こんな風に5人の人格(ってほど分けられてはいないけど)にホめられると、ちょっといい気分になりそうなのが怖いですw

参考

GitHub https://github.com/moonmile/echo-cham-baachan

Vercel https://echo-cham-baachan.vercel.app で動作確認

設定から「陰謀論型」を選んだ時。

カテゴリー: 開発 | エコーチェンばーちゃんの開発 はコメントを受け付けていません

vscode + copilot の plan モードを実務の計画に応用できるか確認してみよう

別に Claude Code のプランモードが駄目という訳ではないのですが、IT 屋の範疇でいる “Plan” と、実務的な “Plan” とは大きな差があるので、そこだけ高速化したり自動化したりしても仕方がないんですよ。というのが本記事の主旨です。いわゆる、部分最適化に陥り勝ちになるので、じゃあ、全体を見渡した時に昨今の AI エージェントの Plan モードあるいは spec 駆動が、どのように実務に活用できるのか、という実験です。

プログラムを作るとか IT システムを組むとかいう範囲ではなくて、もう少し広い範囲で考えてみます。例えば、次のような要件での入札案件が出て来たとしましょう(実際に案件としてあるわけだし)。実際には入札までの設計とか見積もりとか人員の調達などが入るのですが、じゃあ、実務見積もりとしてどのくらい AI エージェントの機能が貢献できるのか? という実験をしてみます。これ、入口の部分は今書いている書籍に入ってはいるのですが、その続きはどうなるのかわからないので、結論はよくわからないところからスタートします。

Readme.md で目的を立てる

AI に資料を渡さないとスタートができないので、Readme.md にスタートとゴールの記述をします。

# 幼保小の架け橋プログラムに関する調査研究事業のまとめ

## 目的

「幼保小の架け橋プログラムに関する調査研究事業」の各都道府県の成果報告書をまとめる。
成果報告書があがっているが、フォーマットが統一されていないので、内容を整理する必要がある。
これを整理しつつ、目的のポイントを抑えて、参考となりそうな上位5件程度をピックアップしてまとめる。

## 進め方

1. 各都道府県の成果報告書を収集する。
2. ピックアップする内容、ポイントを抽出する。
3. ピックアップ項目に対して、各都道府県別の調査報告書から抜き出して、まとめる。
4. まとめた内容をレビューし、注目すべき上位5件ずつを選定する。
5. 最後に、ピックアップする項目単位で目次を作り、報告書を PDF にまとめる。

## 収集元

- 幼保小の架け橋プログラム:文部科学省 https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00002.htm
- 【幼保小の架け橋プログラム】中間成果報告会発表資料:文部科学省 https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00029.htm
- 【幼保小の架け橋プログラム】令和5年度成果報告書:文部科学省 https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00028.htm
- 【幼保小の架け橋プログラム】事業成果報告会(令和7年2月10日):文部科学省 https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00035.htm

## ピックアップ項目

- 全体的に、都道府県名、総人口、施設数を取りだす。
- 本プログラムの主旨に沿っているかチェックする
- 本プログラムの成果を確認する
- 本プログラムの次回の課題を確認する
- カリキュラム中の議事録やワークシートのリンク先をピックアップする

ピックアップ項目は、本プログラムの成果物をまとめた段階で、追加・修正する可能性がある。

いわゆる、入札案件の要件定義を自分たちで書き出すのと、見通してとしての計画を立てておきます。このあたりは、経験が必要なので IT 業界で2,3年では難しいかもしれませんが、逆に言えば非ITの実務業界としては、このあたりがスタート地点になりますよね。さらっと、A4 の用紙に1枚で書けるようになるといいです。

で、これはたたき台なので、その前身として AI を使ってもかまいません。ただし、AI の提案するプランはフルスペックなものが多いので、適度に端折らなければいけません。いわゆる、適度な予算で組めるような計画を立てないいけないのです。コツとしては、松竹梅で作っておいて、竹になるように工夫するわけですが、そのあたりは別のノウハウですね。

軽く「進め方」を書いてありますが、これは非 IT 業界の人でも可能です。

  • なんらかの形で「成果物報告書」を収集する(手作業でも可)
  • なんらかの形で「成果物から内容をピックアップする(手作業…というか、人が読むのも可)

というわけで、大抵は人手で頑張ります。基本 IT 屋さんも人手に頼るところもあるのですが、そこは IT 業界の強みとして、何らかのツールを使って高速化/省力化していきたいです。さらに言えば、AI エージェントなどの力を借りて、労力を下げたり、ひとよりも精度をアップさせたいわけです。そこが、非 IT の人との差別化になりますよね。IT に関してはプロなわけですから、道具を活用できるように少し頭を働かせてみようというわけです。

Plan.md を作成する

では、vscode + copilot を “plan” モードに切り替えて、データ収集とまとめのプランを立てて貰いましょう。

Plan モードって、手順を作るモードじゃなくて手順通りに進めるというモードなんですかね?このあたりが不明だけど、まあ、リンク先のファイルを読み込んで「19自治体の成果物」ってのは、たしかに正確ですね。

impliments とeditor があるので「editor」を選択しました。多分、impliments は即実行してしまうので危ういです。いったん editor で plan.prompt.md を確認しておきましょう。

# Plan: 幼保小の架け橋プログラム調査研究のまとめ

readme.md の目的を達成するため、19自治体の成果報告書(PDF形式)を収集・整理し、ピックアップ項目に基づいて上位5件を選定、最終レポートとしてPDFにまとめる計画です。

---

## Steps

1. **フォルダ構造の作成** - `data/`(年度別PDF保存用)、`analysis/`(比較分析用)、`output/`(最終成果物用)を readme.md と同階層に作成

2. **PDF一括ダウンロードスクリプトの作成** - 令和5年度・6年度成果報告書、中間報告会資料、事業成果報告会資料を19自治体分(計約70〜100ファイル)を自動取得するPowerShellまたはPythonスクリプトを作成

3. **基本情報収集シートの作成** - 19自治体(北海道、岐阜県、滋賀県、広島県、山口県、高知県、大館市、白石市、西会津町、川越市、横浜市、袋井市、掛川市、京都市、枚方市、箕面市、津和野町、高松市、竹田市)の総人口・施設数をExcel/CSVにまとめる

4. **ピックアップ項目の抽出と比較表作成** - 各報告書から「主旨との整合性」「成果」「次回課題」「カリキュラム・ワークシートのリンク」を抽出し、`analysis/summary.xlsx`に一覧化

5. **スコアリング基準に基づく上位5件の選定** - 先進性、エビデンスの質、再現可能性、地域特性の多様性を考慮して評価・ランキング

6. **最終レポートPDFの作成** - ピックアップ項目単位の目次構成で、選定した5自治体の詳細分析をまとめてPDF化

---

## Further Considerations

1. **PDFテキスト抽出ツールの選定** - 手作業で読み込むか、PyMuPDF/pdfplumberで自動抽出するか?(自動化推奨:19自治体×複数ファイル)

2. **評価基準の重み付け** - 「主旨との整合性」「成果の具体性」「課題の明確さ」のどれを優先するか?事前に決定が必要

3. **年度の扱い** - 令和5年度と6年度の両方を分析対象にするか、最新の6年度(最終年度)のみに絞るか?

---

## 対象自治体一覧(19自治体)

### 県レベル(6件)
| 自治体 |
|--------|
| 北海道 |
| 岐阜県 |
| 滋賀県 |
| 広島県 |
| 山口県 |
| 高知県 |

### 市町村レベル(13件)
| 自治体 | 所在県 |
|--------|--------|
| 大館市 | 秋田県 |
| 白石市 | 宮城県 |
| 西会津町 | 福島県 |
| 川越市 | 埼玉県 |
| 横浜市 | 神奈川県 |
| 袋井市 | 静岡県 |
| 掛川市 | 静岡県 |
| 京都市 | 京都府 |
| 枚方市 | 大阪府 |
| 箕面市 | 大阪府 |
| 津和野町 | 島根県 |
| 高松市 | 香川県 |
| 竹田市 | 大分県 |

---

## 推奨フォルダ構造

```
h:\ai-plan-junior-school-research\
├── readme.md
├── data\
│   ├── r5_reports\           # 令和5年度成果報告書
│   ├── r6_reports\           # 令和6年度成果報告書
│   ├── interim_reports\      # 中間報告会資料
│   └── final_reports\        # 事業成果報告会資料
├── analysis\
│   └── summary.xlsx          # 比較分析シート
└── output\
    └── final_report.pdf      # 最終成果物
```

---

## 収集元URL

- 幼保小の架け橋プログラム:https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00002.htm
- 中間成果報告会発表資料:https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00029.htm
- 令和5年度成果報告書:https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00028.htm
- 事業成果報告会(令和7年2月10日):https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00035.htm

フォルダーの構成とかを作ってくれますね。このあたり、非IT業界だと日本語で作るところですが、IT屋さんとしては Python をあたりを使って自動化したいところなので、このまま使います。多分、フォルダ構成を日本語に直しても AI エージェントのほうでうまくやってくれるはずです。

Steps が具体的な手順で、Further Considerations が設定しなければいけない要素ですね。対象自治体一覧はサイト https://www.mext.go.jp/a_menu/shotou/youchien/1258019_00035.htm をざっと見て確認しておいてください。うまく抽出できている模様です。これは文部科学省のサイトがうまく構造化されているということですね。場合によっては、このあたりが読み込めないサイトもあるでしょうから注意が必要です。たぶん、JavaScript を利用した CSR のサイトは難しいので、そのあたりは手作業が必要かもしれません。

Further Considerations に追加する

## Further Considerations

1. **PDFテキスト抽出ツールの選定** - 手作業で読み込むか、PyMuPDF/pdfplumberで自動抽出するか?(自動化推奨:19自治体×複数ファイル)

Python の PyMuPDF を使って自動収集する


2. **評価基準の重み付け** - 「主旨との整合性」「成果の具体性」「課題の明確さ」のどれを優先するか?事前に決定が必要

「成果の具体性」を最優先にして評価を行って。


3. **年度の扱い** - 令和5年度と6年度の両方を分析対象にするか、最新の6年度(最終年度)のみに絞るか?

令和5年度と6年度の両方を分析対象にして。

たぶん、それぞれの項目に設定値を書いておけばいいはずです。このあたりは、うまく AI エージェントが読み取ってくれます。

この後、Plan で動かすのか Agent で動かすのかが不明なのですが、Plan モードにしたままで、Plan.prompt.md を実行してもらいましょう。

ここでは「Start Implimentation」のボタンをクリック…できあがるのかな?

自動承認を有効にするか?

vscode + copilot で動かしたときに、Allow ボタンがでて、いちいちステップ実行の確認を取ります。これ、真夜中に動かしたいばあいは「自動承認」と言う形でやってしまうのがいいのですが、現時点では悪意ある MCP の防御ができなかったり、サイトの読み込み時に不備が発生しそうなので、人間がいちいち承認するほうがよいです。

何回か同じプロンプトを動かして、安全とわかっている場合にはいいのですが、最初に動かすプロンプトの場合は要注意かなと。

あと、このスクリプトのままだと Python の仮想環境を AI エージェントが作るので初期設定がちょっと面倒ですね。Python の仮想環境だけは、あらかじめ手作業で作っておいたほうがよさそうです。

ダウンロードスクリプトとか、もうベタに作ってくれます。

# -*- coding: utf-8 -*-
"""
幼保小の架け橋プログラム - 成果報告書PDFダウンロードスクリプト
文部科学省の成果報告書ページから19自治体分のPDFを一括ダウンロードします。
"""

import os
import requests
from pathlib import Path
from urllib.parse import urljoin
import time

# ベースURL
BASE_URL = "https://www.mext.go.jp"

# 保存先ディレクトリ
DATA_DIR = Path(r"h:\ai-plan-junior-school-research\data")

# 令和5年度成果報告書(2024年7月9日公開)
R5_REPORTS = {
    "北海道": {
        "成果報告書": "https://www.mext.go.jp/content/20240709-mxt_youji-000023526-1.pdf",
        "カリキュラム": "https://www.mext.go.jp/content/20240709-mxt_youji-000023526-2.pdf",
        "成果物": "https://www.mext.go.jp/content/20240709-mxt_youji-000023526-3.pdf",
    },
    "岐阜県": {
        "成果報告書": "https://www.mext.go.jp/content/20240709-mxt_youji-000023526-4.pdf",
        "カリキュラム": "https://www.mext.go.jp/content/20240709-mxt_youji-000023526-5.pdf",
    },
    "滋賀県": {
        "成果報告書": "https://www.mext.go.jp/content/20240709-mxt_youji-000023526-6.pdf",
        "カリキュラム": "https://www.mext.go.jp/content/20240709-mxt_youji-000023526-7.pdf",
    },
...

見ると分かるのですが、ファイル名がべた書きですよね。これはこれでいいのです。治具=使い捨てのツールとして、Python スクリプトが用いられているわけで、これをファイルから読み込むとか何かの設定から読み込むとかしなくて十分なのです。ダウンロードスクリプトなんて1回しか動かさないし、ファイル名なんて固定で良い訳です。

これがプログラマだったり、プログラムを外注したりするとあれこれと設計やら打合せが必要になるのですが、AI エージェントががりがりとベタで作ってくれるので、その分手間が省けます。逆に言えば、ちょっと修正したい場合(自治体を増やしたい場合とか)は、Plan.prompt.md に自治体とリンク先を追加して、もういちど AI エージェントを使って実行して貰えばいいのです。

このあたりの繰り返しが非常に高速になったのが AI エージェントの利点です。どこのブログだったかツイートだったか忘れましたが「ウォーターフォール開発のプロセスを 15 分で廻せるようになった」のが大きいです。いままでは、この部分をアジャイル開発だとか計画駆動とかで設計を練る、あるいは動作させてから考え直す、というループが必要だったのですが、AI エージェントがすべてをやり直してくれるので「高速にまわる計画駆動」ができるようなったのが大きいですね。

まあ、この部分はPDF のダウンロード&分析という比較的やりやすいところをターゲットにしているので AI エージェントが有効に働きますが、これが部品の品質点検&改良とか、食品加工での調味料の組み合わせ試験、とかになると AI エージェントだけでは回らないので工夫が必要です。そういうときは別な計画が必要になります。他にも、組み込み機器で実測とか、Android 機器の BLE 相性あわせ(これをやっている)とかは AI エージェントだけでは回りません。

分析の完了

さて、2時間ほど掛かりましたが分析レポートが作成されました。

このレポートの点数付けは analyze_reports.py の analyze_report 関数で計算しているので、単純な加点形式なのですが、ひとまず下調べとしてはよいでしょう。

def analyze_report(municipality: str) -> dict:
    """
    自治体の成果報告書を分析する
    
    Args:
        municipality: 自治体名
    
    Returns:
        分析結果の辞書
    """
    result = {
        "自治体名": municipality.replace("_", "(") + ")" if "_" in municipality else municipality,
        "成果報告書": "",
        "主旨との整合性": "",
        "主な成果": "",
        "次回の課題": "",
        "カリキュラム・ワークシート": "",
        "成果の具体性スコア": 0,
        "備考": ""
    }
    
    # 令和5年度成果報告書を読み込む
    r5_report_path = DATA_DIR / "r5_reports" / f"{municipality}_成果報告書.pdf"
    r5_text = ""
    if r5_report_path.exists():
        r5_text = extract_text_from_pdf(r5_report_path)
        result["成果報告書"] = "あり"
    else:
        result["成果報告書"] = "なし"
        result["備考"] = "令和5年度成果報告書が見つかりません"
    

本来ならば、ピックアップしたい用語などを含めて PDF を OpenAI API に流し込んで、AI にスコア判定をさせる方法がよいです。これをやるためには、plan.prompt.md を書き変えて、評価の重みづけをするツールを Python スクリプトを作るのがよいです。このあたりは、プログラマの領分になってしまうので、またの機会にしましょう。

スコア計算のツールを AI エージェントを使って作るればいいだけなのですが、プロンプトの作成と OpenAI の API キーコードが必要になってしまうので、ちょっと非 IT 業界の人には荷が重いかもしれません。このあたり、自動化をしないのであれば、ChatGPT に PDF を手作業で突っ込みながらも可能なので、要件自体は満たせます。

final_report.md を python の md-to-pdf を使って PDF ファイルに直したものです。スコアをどう見るかは、中身に確認していかないと駄目なのですが、下調べ段階としてはこれで十分でしょう。すべてを調ベルのは大変なので、優先度としてスコアの高いものから順にチェックしていけばいいのです。そこは AI の判断を信用するかどうかは別なのですが。

まあ、こんな感じで非 IT 業界でも vscode + copilot の AI エージェントを使って資料作成とか情報収集とかができるようになりますよ、という例です。特に、この手のツールをを外注したり自前で時間をかけて作る必要がなくなるのが良いかなと。Plan のほうは、自分で計画立てないと途中で頓挫しそうですが、少しは頼りになるでしょう。

参考先

https://github.com/moonmile/ai-plan-junior-school-research

追記 OpenAI API を使う

もう少し実用的な例を含めてみようと思って、OpenAI API を使ってそれぞれの報告書のスコアを出してみましょう。

Plan.prompt.md に新しい評価基準を追加します。

### 評価基準(成果の具体性スコア: 最大100点)

評価基準関数 analyze_report を OpenAI API を使って再実装する
- analyze_report_ai 関数とする


- 成果報告書の有無 20点
- 主旨との整合性 20点
  主要キーワードが上手く織り込まれているかをチェック
- 主な成果 20点
  成果の主張や具体的なデータ・事例の提示がされているかをチェック
- 次回の課題 20点
  問題点を明らかにして、次回以降の課題が具体的に締召されているかをチェック
- カリキュラム・ワークシートの有無 20点
  カリキュラムやワークシートのリンクが提供されているかをチェック

この内容を OpenAI のプロンプトと成果物の文書を一緒に渡して、評価結果を返して貰う事になります。以前は、システムプロンプトを作ったり、OpenAI API を呼び出す関数を作ったりと色々大変でしたが、いまでは AI エージェントが適切なコードを作ってくれます。

ここからはプログラマの領域に踏み込むので、非 IT 業界の方には少し辛いかもしれませんが、大丈夫です。概ねは AI エージェントがやってくれます。が、一発ではうまく動かない(大抵は JSON 形式が喰い違ってうまくいかない)のですが、これも AI エージェントが少しずつ直していってくれます。

このあたりが、AI エージェントが Excel VBA のように一般に広まるかどうかの肝ですね。いわゆる、バグ直しをしないといけないので、これは従来のプログラミングとほとんど変わりません。コードを書かない代わりに、不具合を見つけてうまく AI に指示を出すとか、自分が納得できるまで AI エージェントを働かせ続けることが必要になります。つまりは、根気がいるわけですが…ここはちょっと分かりません。私はプログラマなので慣れているのですが、一般の人はどう思うのか?

結果として、こんな風に Excel 形式で分析結果がでます。

それぞれのスコアもそれっぽく出ているので、プログラムの動きとしては問題なさそうです。

システムプロンプトに関しては、以下のようになっているので、この内容を書き変えれば別の評価基準になります。

# OpenAI クライアント(環境変数 OPENAI_API_KEY を自動的に使用)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# 評価プロンプト
EVALUATION_PROMPT = """あなたは「幼保小の架け橋プログラム」の成果報告書を評価する専門家です。
以下の自治体の成果報告書を評価してください。

## 評価対象自治体
{municipality}

## 成果報告書の内容(抜粋)
{report_text}

## カリキュラム・成果物の有無
{materials_info}

## 評価基準(最大100点)

以下の5つの観点で各20点満点で評価してください:

### 1. 成果報告書の有無・充実度(20点)
- 成果報告書が存在するか
- 報告書の内容が充実しているか
- 構成が整理されているか

### 2. 主旨との整合性(20点)
以下の主要キーワードが適切に織り込まれているかをチェック:
- 架け橋期、5歳児、小学校1年生
- 連携、接続
- 主体的、対話的、深い学び
- 多様性、学びの基盤

### 3. 主な成果(20点)
- 成果の主張が明確か
- 具体的なデータや数値が提示されているか
- 実践事例が具体的に記載されているか
- エビデンスに基づいた成果報告になっているか

### 4. 次回の課題(20点)
- 問題点が明確に示されているか
- 次回以降の課題が具体的に提示されているか
- 改善に向けた方向性が示されているか

### 5. カリキュラム・ワークシートの有無(20点)
- カリキュラムが提供されているか
- ワークシートや成果物が提供されているか
- 他自治体でも活用可能な形式か

## 出力形式
以下のJSON形式で回答してください(コードブロックなしで純粋なJSONのみ):

{{"成果報告書スコア": 15, "成果報告書評価": "評価コメント", "主旨整合性スコア": 18, "主旨整合性評価": "評価コメント", "成果スコア": 16, "成果評価": "評価コメント", "課題スコア": 14, "課題評価": "評価コメント", "成果物スコア": 12, "成果物評価": "評価コメント", "総合スコア": 75, "総合評価": "総合的な評価コメント", "主な成果要約": "成果の要約(200文字以内)", "主な課題要約": "課題の要約(200文字以内)"}}
"""

いままで、このプロンプトを書くのが大変で「プロンプトエンジニアリング」と呼ばれていたものですが、こんな風にレポートを出したり要約を出す程度であれば、AI エージェントが作成してくれるプロンプトで構いません。それほど手を入れなくて済みます。

まあ、謎な画像AIのプロンプトならば別なのでしょうが、OpenAI API などを使って AI とやり取りするための JSON 形式や内容を変換するようなプロンプトをを作る場合には AI エージェント自身に作成してもらったほうがいいでしょう。

コード自体は、https://github.com/moonmile/ai-plan-junior-school-research/tree/openai のほうにブランチ openai にしておくので、そこからダウンロードしてください。

レポートを見ると、分析手法が書いてあります。成果の具体性スコアも変わっています。これれは、実際に報告書を読んで人間が判断する必要があります。

カテゴリー: 開発 | vscode + copilot の plan モードを実務の計画に応用できるか確認してみよう はコメントを受け付けていません

数学的な問題について画像AIによる嘘を避ける方法

この手の話は、DALL-E の頃から言われていて、何かと数学的な図を書かせようとするとどこからかのなんちゃって画像を持ってくるために変なことになります。たぶん、古い教科書のスキャン画像とかを学習データにいれてしまっていて、そこから引っ張ってきているだけです。そもそも、画像の生成 AI に関しては、「教師なし学習」での推論でしかなくて、数学や物理のような自然科学的な解答のある「教師あり学習」の結果を求めようとしても無駄です。このあたりは、漫画やイラストを描かせた場合にはなんとなくいいけれど、部分的に手の指がおかしかったり腕が三本あったりするのがそれです。このあたりの正確性≒正解と明確に分かるものは、将来的にAIエージェントによる自己チェック機能で避けることができると思います。

まあ、その自己チェックを入れてあげれば、この手の図も正確にできるようになると、という例が以下のものです。

現状の唯一の方法としては、先の「シュレディンガー方程式」については、直接画像AIを使うのではなく、いったん Python などを使って正確な式から正確な図を描くようにします。こうすると、当たり前ですが正確な図ができますね。これを、切り貼りする(素材として画像AIに渡してもいいでしょう)ことで、「数学的に正確な図」を私達は得ることができます。

当たり前といえば、当たり前なのですが、この手のなんちゃって画像AI全般に言えることで、プロンプトがどうという話ではなく、さきに書いた通り

・「教師なし学習」的な正解ではないもの、つまりは「創造性」みたいなものを求めるのか?
・「教師あり学習」的な数学や物理のように自然科学として正解があるものを求めるのか?

という違いです。先の X のポストから言えば、当然後者のほうではあるのですが、実は意図として「数学的には正しくはないけれど、なんとなく中世で扱っていた数学っぽい SF 的な図を作り出して」というプロンプトであれば、前者が求められるというわけです。

で、実験的に Python でコードを出力もらったらどうなのか? ということを試してみましょう。以下は「Claude Sonnet 4.5」を使って Python コードを出力したものです。画像は、matplotlib.pyplot を使って PNG 形式で得ることができます。

ランダム値

プロンプトで「ランダム値をプロットして」というと、これもなんちゃって画像が生成されます。実際にランダム値の正確な図が欲しいのであれば、Python コードで書けばいいだけです。さぼってはいけませんw

# 一様分布のグラフを描く
import numpy as np
import matplotlib.pyplot as plt

# 日本語フォントの設定
plt.rcParams['font.sans-serif'] = ['MS Gothic', 'Yu Gothic', 'Meiryo']
plt.rcParams['axes.unicode_minus'] = False

# 一様分布からランダム値を生成
np.random.seed(42)  # 再現性のため
n_samples = 10000

# 0から1の範囲で一様分布
uniform_values = np.random.uniform(0, 1, n_samples)

# グラフの作成
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# 1. ヒストグラム
axes[0, 0].hist(uniform_values, bins=50, edgecolor='black', alpha=0.7)
axes[0, 0].set_title('一様分布のヒストグラム')
axes[0, 0].set_xlabel('値')
axes[0, 0].set_ylabel('度数')
axes[0, 0].grid(True, alpha=0.3)

# 2. 累積分布
axes[0, 1].hist(uniform_values, bins=50, cumulative=True, edgecolor='black', alpha=0.7)
axes[0, 1].set_title('累積分布')
axes[0, 1].set_xlabel('値')
axes[0, 1].set_ylabel('累積度数')
axes[0, 1].grid(True, alpha=0.3)

# 3. 散布図(サンプル順)
sample_indices = np.arange(min(500, n_samples))
axes[1, 0].scatter(sample_indices, uniform_values[:len(sample_indices)], alpha=0.5, s=10)
axes[1, 0].set_title('ランダム値の分布(最初の500サンプル)')
axes[1, 0].set_xlabel('サンプル番号')
axes[1, 0].set_ylabel('値')
axes[1, 0].grid(True, alpha=0.3)

# 4. 理論値との比較
sorted_values = np.sort(uniform_values)
theoretical = np.linspace(0, 1, n_samples)
axes[1, 1].plot(theoretical, sorted_values, 'b-', alpha=0.5, label='実測値')
axes[1, 1].plot([0, 1], [0, 1], 'r--', label='理論値(y=x)')
axes[1, 1].set_title('Q-Qプロット(理論値との比較)')
axes[1, 1].set_xlabel('理論分位点')
axes[1, 1].set_ylabel('実測分位点')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('一様分布.png', dpi=300, bbox_inches='tight')
plt.show()

print(f'サンプル数: {n_samples}')
print(f'平均値: {np.mean(uniform_values):.4f} (理論値: 0.5000)')
print(f'標準偏差: {np.std(uniform_values):.4f} (理論値: {1/np.sqrt(12):.4f})')
print(f'最小値: {np.min(uniform_values):.4f}')
print(f'最大値: {np.max(uniform_values):.4f}')

波動関数


波動関数をグラフにしたものです、波動関数自体は数学的な式なので、画像AIに頼るよりも Python などで正確に描いたほうが良いです。ここぐらいまでは、コードやグラフを眺めて、ほぼ出ているだろうというレベルでしょう。


# シュレディンガー方程式の「波動関数」と「確率密度」
# 波動関数の例を 2D グラフで
# 確率密度のグラフを 3D グラフで

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 日本語フォントの設定
plt.rcParams['font.sans-serif'] = ['MS Gothic', 'Yu Gothic', 'Meiryo']
plt.rcParams['axes.unicode_minus'] = False

# 1次元無限井戸型ポテンシャルの波動関数
def wave_function_1d(x, n, L):
    """
    1次元無限井戸型ポテンシャルの波動関数
    n: 量子数 (1, 2, 3, ...)
    L: 井戸の幅
    """
    return np.sqrt(2/L) * np.sin(n * np.pi * x / L)

# 2次元波動関数(例:水素原子の2p軌道)
def wave_function_2d(x, y):
    """
    簡易的な2次元波動関数の例
    """
    r = np.sqrt(x**2 + y**2)
    return r * np.exp(-r) * np.cos(np.arctan2(y, x))

# 確率密度関数(波動関数の絶対値の2乗)
def probability_density(psi):
    """
    確率密度 = |ψ|²
    """
    return np.abs(psi)**2

# ===== 1. 波動関数の2Dグラフ =====
fig = plt.figure(figsize=(16, 10))

# 1次元波動関数(複数の量子状態)
x = np.linspace(0, 1, 1000)
L = 1.0

ax1 = plt.subplot(2, 3, 1)
for n in [1, 2, 3, 4]:
    psi = wave_function_1d(x, n, L)
    ax1.plot(x, psi, label=f'n={n}')
ax1.set_xlabel('位置 x')
ax1.set_ylabel('波動関数 ψ(x)')
ax1.set_title('1次元無限井戸型ポテンシャルの波動関数')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.axhline(y=0, color='k', linestyle='-', linewidth=0.5)

# 確率密度(1次元)
ax2 = plt.subplot(2, 3, 2)
for n in [1, 2, 3, 4]:
    psi = wave_function_1d(x, n, L)
    prob = probability_density(psi)
    ax2.plot(x, prob, label=f'n={n}')
ax2.set_xlabel('位置 x')
ax2.set_ylabel('確率密度 |ψ(x)|²')
ax2.set_title('確率密度(1次元)')
ax2.legend()
ax2.grid(True, alpha=0.3)

# 波動関数の実部と虚部(時間発展を含む例)
ax3 = plt.subplot(2, 3, 3)
t = 0
n = 2
psi = wave_function_1d(x, n, L)
E = n**2  # エネルギー固有値(簡略化)
psi_real = psi * np.cos(E * t)
psi_imag = psi * np.sin(E * t)
ax3.plot(x, psi_real, label='実部 Re(ψ)', color='blue')
ax3.plot(x, psi_imag, label='虚部 Im(ψ)', color='red')
ax3.plot(x, np.abs(psi), label='振幅 |ψ|', color='green', linestyle='--')
ax3.set_xlabel('位置 x')
ax3.set_ylabel('波動関数')
ax3.set_title(f'波動関数の実部・虚部(n={n}, t={t})')
ax3.legend()
ax3.grid(True, alpha=0.3)
ax3.axhline(y=0, color='k', linestyle='-', linewidth=0.5)

# ===== 2. 2次元確率密度の3Dグラフ =====

# 2次元グリッド
x_2d = np.linspace(-5, 5, 100)
y_2d = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x_2d, y_2d)

# 波動関数を計算
psi_2d = wave_function_2d(X, Y)
prob_2d = probability_density(psi_2d)

# 3Dプロット
ax4 = plt.subplot(2, 3, 4, projection='3d')
surf = ax4.plot_surface(X, Y, prob_2d, cmap='viridis', alpha=0.8)
ax4.set_xlabel('x')
ax4.set_ylabel('y')
ax4.set_zlabel('確率密度 |ψ|²')
ax4.set_title('2次元確率密度(3D表示)')
plt.colorbar(surf, ax=ax4, shrink=0.5)

# 2Dヒートマップ(上から見た図)
ax5 = plt.subplot(2, 3, 5)
contour = ax5.contourf(X, Y, prob_2d, levels=20, cmap='viridis')
ax5.set_xlabel('x')
ax5.set_ylabel('y')
ax5.set_title('確率密度(ヒートマップ)')
ax5.set_aspect('equal')
plt.colorbar(contour, ax=ax5)

# 等高線プロット
ax6 = plt.subplot(2, 3, 6)
contour_lines = ax6.contour(X, Y, prob_2d, levels=15, colors='black', linewidths=0.5)
ax6.contourf(X, Y, prob_2d, levels=20, cmap='plasma', alpha=0.7)
ax6.clabel(contour_lines, inline=True, fontsize=8)
ax6.set_xlabel('x')
ax6.set_ylabel('y')
ax6.set_title('確率密度(等高線)')
ax6.set_aspect('equal')

plt.tight_layout()
plt.savefig('波動関数と確率密度.png', dpi=300, bbox_inches='tight')
plt.show()

# 統計情報を出力
print("=" * 50)
print("1次元波動関数の統計(n=1の場合)")
print("=" * 50)
psi_1 = wave_function_1d(x, 1, L)
prob_1 = probability_density(psi_1)
print(f"波動関数の最大値: {np.max(np.abs(psi_1)):.4f}")
print(f"確率密度の積分(規格化確認): {np.trapz(prob_1, x):.4f}")
print(f"期待値 <x>: {np.trapz(x * prob_1, x):.4f}")

print("\n" + "=" * 50)
print("2次元確率密度の統計")
print("=" * 50)
total_prob = np.sum(prob_2d) * (x_2d[1] - x_2d[0]) * (y_2d[1] - y_2d[0])
print(f"確率密度の総和(近似): {total_prob:.4f}")
print(f"最大確率密度: {np.max(prob_2d):.6f}")
max_idx = np.unravel_index(np.argmax(prob_2d), prob_2d.shape)
print(f"最大確率密度の位置: (x={X[max_idx]:.2f}, y={Y[max_idx]:.2f})")

水素原子の電子軌道

実は、水素原子の軌道計算がちょっとアヤシイです。自分の場合は、原子力学科なのでこの軌道計算は最初の頃にやった筈なのですが(まあ、波動関数自体はいわゆる常識の範囲なので)、図の書き方が一般的に流通するものと違うのでなんとも言えないです。

ただし、電子軌道計算は、https://betterlate-thannever.github.io/Chemistry-2e/%E7%AC%AC6%E7%AB%A0-%E5%85%83%E7%B4%A0%E3%81%AE%E9%9B%BB%E5%AD%90%E6%A7%8B%E9%80%A0%E3%81%A8%E5%91%A8%E6%9C%9F%E7%9A%84%E6%80%A7%E8%B3%AA.html#%E9%87%8F%E5%AD%90%E8%AB%96%E3%81%AE%E7%99%BA%E5%B1%95 にあるように描かれるのが定番ではあるのですが、実際には電子雲の確率として示されるので、ちょっと旧来の書き方はいわゆる電子の粒のイメージが強くて、微妙なんですよね。確かに、私も「ダンベル型」になるという覚えがあるのですが、このイメージよりも単純な電子雲つまり数式そのものとしてはあくするようないい気がしています。まあ、どちらにせよ、模式図でしかないので、計算するとこうなるよという Python コードと図です。

# 水素原子の電子軌道の 3D グラフ
# s、p、f、d 軌道も追加

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.special import sph_harm_y, genlaguerre, factorial
import matplotlib.colors as mcolors

# 日本語フォントの設定
plt.rcParams['font.sans-serif'] = ['MS Gothic', 'Yu Gothic', 'Meiryo']
plt.rcParams['axes.unicode_minus'] = False

# ボーア半径(原子単位)
a0 = 1.0

def radial_wave_function(r, n, l):
    """
    動径波動関数 R_nl(r)
    n: 主量子数
    l: 軌道角運動量量子数
    """
    rho = 2 * r / (n * a0)
    norm = np.sqrt((2 / (n * a0))**3 * factorial(n - l - 1) / (2 * n * factorial(n + l)))
    laguerre = genlaguerre(n - l - 1, 2 * l + 1)(rho)
    return norm * np.exp(-rho / 2) * rho**l * laguerre

def hydrogen_orbital(r, theta, phi, n, l, m):
    """
    水素原子の波動関数 ψ_nlm(r, θ, φ)
    n: 主量子数 (1, 2, 3, ...)
    l: 軌道角運動量量子数 (0, 1, ..., n-1)
    m: 磁気量子数 (-l, ..., 0, ..., l)
    """
    R_nl = radial_wave_function(r, n, l)
    Y_lm = sph_harm_y(l, m, theta, phi)
    return R_nl * Y_lm

def create_orbital_visualization(n, l, m, resolution=50, r_max=None):
    """
    軌道を3D可視化
    """
    if r_max is None:
        r_max = n**2 * a0 * 3  # 適切な範囲を設定
    
    # 球座標グリッド
    theta = np.linspace(0, np.pi, resolution)
    phi = np.linspace(0, 2*np.pi, resolution)
    THETA, PHI = np.meshgrid(theta, phi)
    
    # 確率密度の最大値を探すための半径
    r_values = np.linspace(0.1, r_max, 100)
    max_prob = 0
    optimal_r = r_max / 2
    
    for r_test in r_values:
        psi = hydrogen_orbital(r_test, np.pi/2, 0, n, l, m)
        prob = np.abs(psi)**2 * r_test**2
        if prob > max_prob:
            max_prob = prob
            optimal_r = r_test
    
    # 複数の半径で等値面をプロット
    r_surfaces = [optimal_r * factor for factor in [0.5, 0.8, 1.0]]
    
    return THETA, PHI, r_surfaces

# 各軌道の名前
orbital_names = {
    (1, 0, 0): '1s',
    (2, 0, 0): '2s',
    (2, 1, -1): '2p_y',
    (2, 1, 0): '2p_z',
    (2, 1, 1): '2p_x',
    (3, 0, 0): '3s',
    (3, 1, -1): '3p_y',
    (3, 1, 0): '3p_z',
    (3, 1, 1): '3p_x',
    (3, 2, -2): '3d_xy',
    (3, 2, -1): '3d_yz',
    (3, 2, 0): '3d_z²',
    (3, 2, 1): '3d_xz',
    (3, 2, 2): '3d_x²-y²',
    (4, 0, 0): '4s',
    (4, 1, 0): '4p_z',
    (4, 2, 0): '4d_z²',
    (4, 3, -3): '4f_y(3x²-y²)',
    (4, 3, -2): '4f_xyz',
    (4, 3, -1): '4f_yz²',
    (4, 3, 0): '4f_z³',
    (4, 3, 1): '4f_xz²',
    (4, 3, 2): '4f_z(x²-y²)',
    (4, 3, 3): '4f_x(x²-3y²)',
}

# 可視化する軌道(s, p, d, f 軌道を含む)
orbitals_to_plot = [
    (1, 0, 0),   # 1s
    (2, 1, 1),   # 2px
    (3, 2, 1),   # 3dxz
    (4, 3, 1),   # 4fxz²
]

fig = plt.figure(figsize=(20, 16))

for idx, (n, l, m) in enumerate(orbitals_to_plot, 1):
    ax = fig.add_subplot(3, 5, idx, projection='3d')
    
    THETA, PHI, r_surfaces = create_orbital_visualization(n, l, m, resolution=60)
    
    # 各半径で等値面をプロット
    for i, r in enumerate(r_surfaces):
        # 直交座標に変換
        X = r * np.sin(THETA) * np.cos(PHI)
        Y = r * np.sin(THETA) * np.sin(PHI)
        Z = r * np.cos(THETA)
        
        # 波動関数を計算
        psi = hydrogen_orbital(r, THETA, PHI, n, l, m)
        
        # 確率密度(実部の符号で色分け)
        prob = np.abs(psi)**2
        phase = np.angle(psi)
        colors = np.real(psi)
        
        # 正負で色分け(vmin < vcenter < vmax を保証)
        vmin, vmax = colors.min(), colors.max()
        if vmin >= 0:
            vmin = -1e-10
        if vmax <= 0:
            vmax = 1e-10
        norm = mcolors.TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)
        
        surf = ax.plot_surface(X, Y, Z, facecolors=plt.cm.RdBu(norm(colors)),
                              alpha=0.7 - i*0.2, shade=True, 
                              linewidth=0, antialiased=True)
    
    # 軸設定
    orbital_name = orbital_names.get((n, l, m), f'{n},{l},{m}')
    ax.set_title(f'{orbital_name} (n={n}, l={l}, m={m})', fontsize=12, fontweight='bold')
    ax.set_xlabel('x (a₀)')
    ax.set_ylabel('y (a₀)')
    ax.set_zlabel('z (a₀)')
    
    # 範囲を統一
    max_range = n**2 * a0 * 2
    ax.set_xlim(-max_range, max_range)
    ax.set_ylim(-max_range, max_range)
    ax.set_zlim(-max_range, max_range)
    
    # 視点を調整
    ax.view_init(elev=20, azim=45)
    
    # グリッドを薄く
    ax.grid(True, alpha=0.2)

plt.tight_layout()
plt.savefig('水素原子の電子軌道.png', dpi=300, bbox_inches='tight')
plt.show()

# 軌道の特徴を出力
print("=" * 60)
print("水素原子の電子軌道の特徴")
print("=" * 60)

for n, l, m in orbitals_to_plot:
    orbital_name = orbital_names.get((n, l, m), f'{n},{l},{m}')
    
    # 動径波動関数の最大値の位置(最も確率の高い半径)
    r_values = np.linspace(0.01, n**2 * a0 * 3, 1000)
    radial_prob = [radial_wave_function(r, n, l)**2 * r**2 for r in r_values]
    max_idx = np.argmax(radial_prob)
    r_max_prob = r_values[max_idx]
    
    print(f"\n{orbital_name}軌道:")
    print(f"  主量子数 n = {n}")
    print(f"  角運動量量子数 l = {l} ({'s' if l==0 else 'p' if l==1 else 'd' if l==2 else 'f'}軌道)")
    print(f"  磁気量子数 m = {m}")
    print(f"  最大確率密度の半径: {r_max_prob:.3f} a₀")
    print(f"  エネルギー準位: E_{n} = -13.6/{n}² = {-13.6/n**2:.3f} eV")

print("\n" + "=" * 60)
print("凡例:")
print("  赤色: 波動関数が正")
print("  青色: 波動関数が負")
print("  透明度: 外側ほど薄く表示")
print("=" * 60)

元ツイ主が理系なのか文系なのかわからないのですが、、数学的・物理的に正確な図を求める場合には、画像AIに頼るのではなく、いったんプログラムコードで正確に描画するほうが正しい図が得られるよ、という老婆心であります。

カテゴリー: 開発 | 数学的な問題について画像AIによる嘘を避ける方法 はコメントを受け付けていません

漫画村関連で「土管」米Cloudflareに賠償命令についての考察

この日経新聞のポストに関して、脊髄反射的に「土管が賠償責任を負うなんて」という意見が付いてるが、考え直したほうがよいです。この問題は単なるインフラ=土管として捉えているのではなくて、契約上「反社」としてわかっているところと契約をした上で、警告を無視して(米クラウドウェアじゃなくて日本営業所なのだろうけど)ずるずると契約し続けていた、というところが大きな問題です。

賠償金額は合計5億円(四社が各社1億2650万円)なので、ほぼ原告通りの金額が通ったと思われます。ただし、月間3億アクセスあることと海賊版の頒布という「幇助」が重なると、実際の被害額はさらに多かったはずですが、この場合「土管」のように見えるインフラ業者も敗訴する場合がある、というところが重要です。

今回の判決のその前に考慮する事柄

海賊版については、昨今の中国のあれこれも含めて、国内ならばなんとかなるものの国外に出てしまうと著作権の扱いが異なったりしてかなりややこしいです。このややこしいところを利用して「漫画村」のような海賊版配布サイトが発生したわけですが、その前提を踏まえておく必要があります。

  • 「漫画村」のような直接的な海賊版配布サイトとその主催者
  • 海賊版サイトの運営者と契約をしている、ホスティングサイト
  • 海賊版サイトに接続する中間通信業者(プロキシサーバー)
  • 海賊版サイトと契約する通信事業者

といういくつかのパターンがあります。このほかにも、海賊版サイトを裏で運営するための WEB サイトの請負業者や保守業者というものがあります。このあたりは、裁判沙汰にはなっていませんが、「反社条項」に照らし合わせると、芋づる式に警察に引っ張られる可能性があるので会社としては注意が必要です。

海賊版配布サイトとその主催者

まずは、海賊版配布サイトとその主催者は、あきらかに犯罪行為となるので、即捕まることになります。すでに刑事処罰を受けた人が出所しているぐらい時間が経っているので、犯罪であることはいまとなっては明らかなことです。ただし、判決として「懲役3年・罰金1,000万円」となるので、出版社等の想定被害総額(200億円程度と言われる)から見ると雀の涙ほどでしかありあません。これは民事訴訟で争うしかありません。

「漫画村」に関する損害賠償請求事件の判決言渡について | ニュースリリース | KADOKAWAグループ ポータルサイト https://group.kadokawa.co.jp/information/news_release/2024041801.html

これ、主催者に請求しても支払い能力がない(広告収入はあっただろうけど、果たしてそれが200億円に達するかわからない)ので、民事では 17 億円の賠償となっているようです。

ホスティングサイトの責任

現時点で「ホスティングサイト」の責任を問うときに、CDNのクラウドフレア社の件しかでてこないので、検証が難しいのですが、過去の例をみるとホスティングサイトにも責任があります。ホスティング会社の場合、コンテンツ業者との契約が発生しますがコンテンツの内容が問われることがあまりありませんでした。しかし、性的なコンテンツや反社的なコンテンツがあるとホスティング業者自体が廃業に追い込まれます。良い例としては FC2 があります。

企業コンプライアンスで次々と性的コンテンツ(静的ではないです)や違法動画を流すことをやめるホスティング業者が多くなっている中、 FC2 はそれらを野放しにしました。というか、経営的にコンテンツの内容を問わない、という形を貫きました。コンテンツの内容を問わないという点では「表現の自由」を支えることにはなるのでしょうが、違法コンテンツの配信をそのままにしておくという「犯罪幇助」に引っ掛かってしまいます。

FC2の法的責任:著作権侵害コンテンツを放置中!米国のDMCA(デジタルミレニアム著作権法)プラットフォーム責任の観点から整理|アメリカ移民ケニー小倉の裏ログ https://note.com/onoshin_digital/n/n816a97c3e878

なので、単なる「場」としてのホスティング業者であっても違法コンテンツの配信を野放しにするわけにはいかないのです。

中間通信業者の例

違法コンテンツは電子的な配信がなされるため直接ユーザーと繋がっているわけではありません。途中に中間通信業者(プロキシサーバー等)を通ります。これは、順法コンテンツだとしてもかわりません。

このあたりを KADOKAWA が規制しようとした事実がありますが、その規制自体は違法です。

発端としたは「漫画村」のサイトに繋がる経路(IPアドレス、DNSサーバー)を制限しようとする法案を国会で通そうとましたが、失敗しています。いわゆる DNS によるブロッキングです。
しかし、これは「通信の秘匿」という意味で違法であり、却下されています。たしかに違法である漫画村サイトに対して DNS ブロッキングを行えば一般ユーザーからは接続はできなくなります。しかし、この DNS ブロッキングの方式は、先の順法サイトへのブロッキングも可能になるという法的な問題が発生してしまいます。
漫画村のような違法サイトであるということを誰が判断するのか?という問題です。 

DNS ブロッキングが通ってしまうと、ときの政府の思惑で「通信の秘匿」が侵される危険性が高いです。実際、中国では金盾でブロッキングされているわけですから、技術的には可能ですよね。抜け道としては VPN で抜けてしまっているのですから、これも違法サイトだけブロックするわけにはいかないことは明白です。

「漫画村」ブロッキング――誰が、どんな経緯で動いたのか – Yahoo!ニュース https://news.yahoo.co.jp/feature/1039/

というわけで、単なるデータを媒介する形での中間通信業者は、この件に関しては違法性はありません。逆に警察や政府が DNS ブロッキングを要請してきたとしても跳ねのけるだけの法的根拠があるというわけです。
まあ、実際のところはどうなのかわかりませんが。NTT が動いてしまった具合ですから。いわゆるときの忖度ですよね。

海賊版サイトと契約する通信事業者

いわゆる「土管」=インフラ業者としてのクラウドフレア社のような場合はどうなるのか?というのが今回の判決です。まず、違法性の伝播としては、今回の反社と契約しているか否かにあります。当然のことながら契約書自体に反社条項が入っていれば、反社(この場合は漫画村の経営者)と契約を結ぶこと自体が違法です。
当初、クラウドフレア社は「違法な契約者かどうかがわからない」ことを主張してきましたが、これは出版社による再三の警告(あるいは通告?)により「違法性が高い」ことは認識されていたとみるでしょう。これを放置したことにより「犯罪の幇助」という判決に至ります。

だから、ある意味では契約時点ではわからないのは仕方がないです。実際に、契約した後に反社になるかもしれないので、契約時点あるいは契約後自体のそれは問われないはずです。しかし、契約している途中で客観的になんらかの形で反社としてわかった場合には、契約を見直すことをしなければいけません。これは、法的というよりも、今回の判決のように裁判に負けてしまうからです。会社として不味いですよね。

金額的にクラウドフレア社にとって5億円という金額は大したことはないでしょう。しかし、今後、コンテンツ業者が訴えた場合に、同じように敗訴になる可能性が高く、海賊版サイトとの契約の見直しが大量に発生する、というところがこの判決の影響になります。

さらなる波及として

おそらく、問題は Meta 等が広げてしまっている違法広告に訴訟が及ぶ可能性が高いです。Meta や Google は、違法広告をそのまま流しています。しかし、それは広告主の問題であって「土管」としての Meta や Google には責任がないという判断を各社がしていますが、これがひっくり返ります。反社であるとわかっていて契約をし続けた場合には、今回のクラウドフレア社のように敗訴する可能性が高くなります。ですから、Meta や Google は広告主と契約を結ぶ場合に、広告主が反社であるか否か、広告主が違法であることを警告された場合にそれに対処しなければいけないという義務(とリスク対処)が発生します。

実際のところ、ネット広告の出品は自動化されており、Meta や Google 自身がほとんど関与していません。「私は反社ではありません」のチェックボックスぐらいでしょう。逆に言えば、その関与していないことが今回の判決において問題になるわけで、次なるは違法広告の問題に発展するでしょうね。原告が誰になるかが微妙ではありますが、何かの集団訴訟にはなると思われます。

関連法案

この手の話は、電気通信事業法の第三号事業者とプロバイダ制限責任法の両方を把握しておきます。前者はホスティング等の通信事業者(仲介的に掲示板提供とかツイッターのようなミニブログとかも含まれる可能性あり)が守るべき義務と、後者は何か訴訟がおこったときに無限責任ではなく有限であるという意味で責任範囲を規定するものです。片方だけ知ってても片手落ちなので、両方知っておいてください。

電気通信事業法における第三号事業者とは?自社の分類を把握しよう! – オーリック・システムズ・ジャパン株式会社 https://www.auriq.co.jp/blog/kaiseidenki-daisango-category/

プロバイダ責任制限法とは|削除請求・開示請求わかりやすく解説 | 誹謗中傷弁護士相談Cafe https://www.fuhyo-bengoshicafe.com/bengoshicafe-260.html

カテゴリー: 開発 | 漫画村関連で「土管」米Cloudflareに賠償命令についての考察 はコメントを受け付けていません

チームを壊す “ブリリアントジャーク” には向き合わないほうがよい多数の理由

元ネタが消えてしまったので、ちょっとここに解説を書いておきます。

まず、ブリリアントジャーク(Brilliant Jerk)が「優秀だけれど協調性に欠け、組織に悪影響を及ぼす人物」という意味通りならば、できるだけ彼/彼女から避けてください、の一択です。Web で検索をすると「受け入れる」案がたくさんあるのですが、小規模のチームの問題ならばなおさらです。これは明確に “チーム殺し” via 「ピープルウェア」にあたるので、チーム自体が崩壊しかねません。なにがどのように優秀なのか? 目の前のチームが何を成し遂げようとしているのかをチームリーダーは見極めなければいけません。

元記事が大人の事情で消えてしまったので、状況だけを示しておきますが、

・アジャイル開発スクラムのメンバーである
・優秀なメンバーとして後から入ったが協調する気がまったくなかった

という場面です。そもそもアジャイル開発スクラムにおいてはチームメンバーの価値観の統一が最優先になります。これはスプリントと呼ばれる2週間の短期間のうちにプロジェクトの目標を次々と成し遂げないといけないために、まさしく “スクラム” を組み一丸となって進むことを示しています。たまにテック系の行動規範として「相手を尊重する」ことが入っているコミュニティを見かけますが、まさしくその通り、同じチームを組む以上チームメンバーを尊重することが必要です。これは、自分の意見を通すだけでなく相手の意見も聞くという意味も含めます。そのような意味でも、同じ価値観がなければ、チームで組むことはできないと他のメンバーも考えるし、スプリントの中で「徹夜」や「残業」などの嫌なことを乗り越えていくことができないでしょう。

そうなんです、アジャイル開発スクラムの場合は、メンバーの合意のもとでは「徹夜もします」ってのが条件だったりするんですよ、実は。

まあ、そんな訳でスクラムマスター(おそらくリーダー役?)が諭して直るパターンであれば、ブリリアントジャークというほどでもないし、ちょっとした勘違いテック野郎だったんじゃないですかね? という具合です。

では、どう対処するのか?の具体例を示しておきます。IT 業界で「優秀だけど嫌な奴」の筆頭といえば、リーナス・トーバルズ氏です。口が悪くて相手への態度も悪い、容赦がないので有名ですね。でも優秀です。Linux を作った人ですし、現在も Linux を現役で支えている人です。最近の業界ではないのですが、IT の黎明期ではこのような天才的な才能があれば、なんとでも行ける部分があるので、結構こういう人が多いです。ちなみに私はリーナス氏が好きか嫌いかというと、いやどちらでもないです。どちらでもない理由は後述しますが、無責任な話でいえば、そういう人も必要ではあるだろうなぁという話です。これも無責任ですが、同じく後で理由を話します。

これ、IT 業界に限らずクラシックの指揮者とか、映画監督とか、アニメとか漫画家とかいますよね。ひとりの才能が拡散されている業界はこの手の「ジャーク(嫌な奴)」でもやってける部分があります。でも、やっていけるのはそれなりに理由(本人ではなく業界としての理由)があるわけで、ひとまず構造的にやれるようにメンバーを組むというのがひとつの方法です。漫才あたりとかテレビ業界もそうですが、「売れればよい」といのがそれです。そのあたりは市場原理もあるので、否定しません。

チーム内のジャークをどう扱うか?

アジャイル開発スクラムではない場合は、なんとかなります。そもそも優秀なのだから(何を以って優秀というのかは明確にしておいてください)、その「優秀さ」を最大限生かせるようにチームを組み直します。いえ、ある意味でチームを解体します。

先に言ったようにジャークは優秀であろうとチーム殺しの一つなので、チームに取り入れてはいけません。しかし、なんらかの事情でチームに取り込まないといけないという状況になったと仮定しましょう。実際、そういう場面はいくつかあります。

・プロジェクトが炎上してしまって、外部からジャークを呼ばないといけない。
・レガシー開発をしていたジャークしか直せないバグが発生した
・社内政治のナントカ(社長の息子とか取引先の縁故とか)でジャークが入ってきた

のような場合は、どうしてもジャーク君を排除できません。これはもう仕方がないです。諦めましょう。このジャーク君にどう対処して、できるだけ早くジャーク君がいらない状況を作ってしまうのが先決です。メンバーの方が大切です。ジャーク君を育てることを考えてはいけません。まあ、三番目の縁故の場合は、ジャーク君に取り入ることも可能なのですが、それは別の話です。

炎上プロジェクトの助っ人やレガシー開発での助っ人みたいなジャーク君に対しては、もうこれは仕方がないので、マネージャが接待してください。この一手で十分です。いわゆる、バスケでいうところのマンツーマンですね。

・ジャーク君が素早く開発できる環境を、ジャーク君専用で整える
・ジャーク君は、朝のミーティングとかチーム内ルールを免除する
・ジャーク君の暴言がチームメンバに向かったら、すかさずマネージャが割り込む

という具合で、本来のチームを持ってください。それがマネージャの役割です。そして、ジャーク君にはさっさと仕事を終えて貰って出て行ってもらいましょう。育てるとかルールを守って貰うとか何とか考えてはいけません。ジャーク君は別の世界の人ですから、私達には関係ないのです。ジャーク君が作る恩恵だけを享受しましょう。それ以外は不要です。対抗しようとしてはいけません。限りなく受け流してください。

社内のジャークをどう扱うか?

どちらかというと、こっちのほうが問題です。Netflix のカルチャーメモにあるように会社としても「ブリリアントジャーク」を排除してしまうのがベストなのでしょうが、まあ、そうはいっても入社してしまったし、というのが日本的な風土の問題ですね。また、リーナス氏のような優秀な(とも言えないけど)人材が会社に入ってきたらどうでしょう? なかなか排除ってわけにもいかんでしょう、という心情はわかります。また、業界にもよっても違うわけで、クラシックの指揮者とか、アニメーションや映画の監督とか、舞台の役者とか、漫才師みたいなのは、真ん中のジャーク君を盛り立てて成り立っている業界的な構造もあるので外すことができません。つまり代替が効かないというパターンです。

ただし、このあたりの構造はダウンタウンの松本氏やフジテレビの問題やジャニーズの問題のように崩壊することが多いので、慎重に扱わねばなりません。ジャーク君は当然のことながら、素行が悪いだけでなく、法的なラインも自分中心に超えてくることが多いので、そのまま扱うと会社自体が崩壊しかねません。これは経営判断となるので取締役以上で決めることですね。一般的な会社員が決められることではありません。

それぞれの会社がどのようにジャーク君を扱うか、業界ごとに異なるでしょうから、ここでは IT 業界の会社の例に絞っておきましょう。

まず、小規模の会社(10人位とか)の場合は、もう駄目です。小規模の会社の場合は、社員=チームメンバーという形になってしまうので、先のスクラムチームと同じ状態になってしまいます。この場合は、社長自らがジャーク君と対峙するしかありません。あるいは、ジャーク君担当の取締役を付けるしかありません。まあ、小規模 IT 会社の場合は、社長そのものがジャーク君の場合が多いので、その場合は社員が逃げてしまうので大丈夫です。よほどジャーク君が優秀でなければ生き残ることができません。他社と付き合わないといけないわけですから、疑似的であれ表面的な協調性は必要ですよね。芸能界でいえば、マネージャを間に挟むところですが、IT 業界の場合そういうものはありませんので潰れてしまいます。リーナス氏の場合は、かなり例外的です。

問題は100人程度の中規模な会社です。会社としてはジャーク君の配属を変えることができるし、ジャーク君の「優秀」という恩恵だけを会社が受け取れる状態になります。迷惑なのは社員のほうですが、これはうまく会社のほうで対処しなくてはいけません。そうしないと次々と退職者が出てきてしまいます。会社として、新人をひとり入れるのに年収の 1/3 程度がかかります。少なくとも一人の退職者が出ると百万円レベルの損失を考えないといけません。ジャーク君が配属された部署からひとり退職者が出るたびに、ジャーク君のボーナスから百万円を差し引いてもよいぐらいです。それぐらいのダメージを覚悟してください。

というわけで、中規模の会社の場合は、ジャーク君に権限を与えないようにします。先の火消しのようにジャーク君を使い潰してください。そのたびに、太鼓持ち風のマネージャを付けて申し送りをすればよいのです。ジャーク君が改心するかどうかはわかりません。

会社としては、ジャーク君の「優秀」な部分を取り出して、別の会社に売ればよいので、それだけで十分です。まあ、それでも Netflix のように排除するほうがいいと思いますけどね。

さて、いわゆる大手 SIer の場合はどうでしょうか?

実はジャーク君は組織の中に一定数、ちらほらと確率的に存在します。中学校のクラスの中でいじめが発生するのと同じぐらいの確率で存在しますね。いわば、組織が進化する上の必要悪…というより、ちょっとした不可欠な要素という面もあります。学校の中のいじめの問題は別な対応が必要なのですが(クラスメンバーが平等という前提があるので)、大手会社の場合は別な方法がとれます。なぜならば、大人数の組織の中では、それなりに上下関係ができてしまうものですし、会社組織としては給与体系とかあり上下があるのが普通だからです。学校の平等とは違います。

だからといって、大手組織の中でジャーク君を放っておくことはできません。先のフジテレビの問題のようになってしまいます。権限を与えてしまうと、権限が権限を呼び、先行き会社を傾きかねない事態に陥ってしまいます。フジテレビの場合は、ジャーク君自体が社長になってしまったので、時代性もあり仕方がないですね、ってのとどうしようもないね、ってのが私からの視点ですが、IT 業界でもたまにありますね。

唯一のうまい方法としては、ジャーク君たちをひとつの部署に集めることです。

実は大手の会社となると確率的に複数名のジャーク君がいます。ジャーク君は弱肉強食の中で弱者に対していじわるなだけなので、ジャーク君達、つまりは複数名をひとつにまとめてしまってください。いわゆる特殊部隊というやつです。ジャーク君自身には権限を与えてはいけないので、タフなマネージャーを据えてください。マネージャはジャーク君に同情はしません。操るだけです。

なんか見たことがあるでしょう?「攻殻機動隊」の公安8課とかパトレーバーの特車二課とかいくつか思い浮かぶはずです。公安8課の部長なんて、ちょうどいい感じでできていますよね。あれ、物語自体はフィクションではありますが、心理描写や組織体系は現実から逃れられないので(リアリティを出すために) “部長” の立ち位置と言動が本物に近いです。もっとも、政治に近いところであれができるかどうか不明ですが(おそらくできないような気がしますが)、理想像はアレには違いありません。

ジャーク君達は、研究職やら R&D やらで頑張って頂けばいいのです。彼らは優秀なので徹夜だとか勤務時間は全然気にしません。当然勤務態度も悪いのですが、そこは特別部署なので御咎めなしです。ただし、成果は出さないといけないので、弱肉強食ですよね。はたして、ジャーク君チームに入りたいかどうかは、一般的にはわかりません。

もちろん、研究職や R&D チームでも礼儀正しく協調的にやるほうがいいのですが、会社に入ってしまったジャーク君たちをどう扱うかという話になるので、こういうパターンもできます、ということです。

退職を促す方法については、閑職に移すとか色々あるので訴えられない方法を選んでください。これは実情がいろいろ世の中にころがっています。

もう 20 年以上前になりますが、マーチン・ファウラー氏が XP 関係で来日したことがあります。彼の講演でよく覚えているのが「私達の世代では、技術力を盾にしてひとりでやることが多かったが、これからチームでの社会性や協調性が必要になってくると思う」という話でした。アジャイル開発の黎明期でもあり、個人的な開発力よりもチーム力が重んじられてきた時期です。それ以前では、プログラマが知識と腕力で犯罪スレスレでもあってもいいから技術力を磨いてのし上がってきたわけですが(ファウラー氏もそうみたいだし)、これからはそうではないです、というのを彼の娘を例にして話していたのが印象的です。

別に仕事なのですから、学校のときのように仲良しこよしでやる必要はありません。時には給与査定もあるのですから、競争することもあるでしょう。しかし、いまどき敢えてジャークである必要はありません。プロジェクトという目的があれば協力し、プロジェクトがなくなれば特に協調しなくてもよいのです。仕事ですから。

そういう意味で、例えばリーナス氏のようなジャーク君が会社にいて、とある部署で業績を上げていたとして(それがジャーク君チームの R&D だとして)、それをどう思うかといえば、無関係ですよね、自分に被害が及ばない範囲であれば、と私は思う訳です。モノホンのリーナス氏はよく知らないので、なんとも言えないんですよね。なので、ここはリーナス氏風のジャーク君がいたとしての仮定のお話です。

カテゴリー: 開発 | チームを壊す “ブリリアントジャーク” には向き合わないほうがよい多数の理由 はコメントを受け付けていません

Visual Studio 2026 と逆引きシリーズの関係

もうこんな重たい IDE を使わくてもいいだろう、という形なのか、名前が「Visual Studio」に戻った Visual Studio 2026 がリリースされました。

Windows、Mac、Linux 用の Visual Studio と VS Code のダウンロード https://visualstudio.microsoft.com/ja/downloads/

インストールした後にスタート画面で「2026」で検索しても出て来ないので、おかしいなぁと思ったら「Visual Studio」になっているという具合ですね。私の PC では互換用に 2019 と 2022 がインストールされているのですが、今回の 2026 は間に挟まっています。ちょっと見つけ辛い。

ちなみに、起動時にスプラッシュ画面やヘルプメニューからの「Visual Studio の登録」を見ると、きちんと “2026” の文字が入っているので区別が付きます。

たぶん、従来の Windows フォームとか ASP.NET Core MVC アプリとかを作っている間は以前と変わらないと思います。.NET が 10.0 なので昔のプロジェクトを開くときは、*.csproj を開いて変えてしまえばよいでしょう。

特筆すべきは、Visual Basic で iOS アプリが作れる(らしい)ことですね。本当に作れるのか不安なのですが、.MAUI ベースじゃなくて、Objective-C ベースを VB に直したコードをがテンプレートになっています。

なんなのですか!この魔改造っぽいプロジェクトはw

逆引きシリーズとの比較

ざっとですが、逆引きの C# 2022 のプロジェクトが Visual Studio 2026 で動作するか確認してみました。

『現場ですぐに使える! Visual C# 2022逆引き大全 500の極意』https://github.com/moonmile/gyakubiki-vcs2022

第1章から第17章までを開いてみる限り、ターゲットフレームワークを「.NET 6.0」から「.NET 10.0」に変更すれば ok です。サンプルコードでは、src 配下に global.json で .NET 6.0 固定にしてあるので、このファイルを削除してください。

第16章 モバイル環境の極意は古い MAUI を使っているので動かない可能性が大です。
第11章 データベース操作の極意 と 第17章 Excelの極意 は、ひとまず NuGet でライブラリを最新にしてください。多分、互換があって動くとは思うのですが、DB のほうはちょっと未検証です。

逆引きシリーズをどうしたものかと思っているのですが、実は 2022 のときにもうこれが最後だろうと思っていたので結構無茶な形で組み込んでいます。VB のほうは、自前のテンプレートを作ったりして無理矢理 C# に合わせたり、ライブラリ化して Android で動かしたりという感じです。
いや、さすがにひとりで1000ページは辛いので、これが最後という感じだった訳ですが。

その後、秀和システムが無くなって、シン・秀和システム…じゃなくて秀和システム新社になったわけですが、逆引きシリーズはどうるのだろう?と思っているのですが、今サイトを見ると ACCESS とかの逆引きが出ているので、続いてはいる模様です。

となると、ここは是非「Visual Basic で作る iOS アプリ」という誰に需要があるかわからない章を追加しないといけないわけで(Visual Basic の逆引きには先に書いた通り、無理矢理 .NET MAUI の章があります)、これを試さねばなりません。というわけで、ひと通り見たら、連絡を出しましょうか。
中味的には AI エージェントを入れてコードを追加して 2022 よりも盛ることは可能なんですよね。ページ数的にはもう一杯一杯だと思うので、ページに合わせて詰め込んでしまうか、コードを少し端折りめにするか。逆引きのコードは基本全文が載っているので、そのあたりは要相談で。

追記

確かに、VB のコードが iOS 上で動いています。SwiftUI は動かないようなので、従来型の storyboard 形式で書かないといけないのでなかなか敷居が高いですね。storyboard 形式は Xcode で作成するか、最近だと Copilot の Agent モードを使っても書いてくれます。
なかみはほぼ Objective-C のコードの引き写しになるので、従来の C# + Xamarin コードを知っている人ならば勘でなんとかなるだろう、とは思うのですが…これって、誰得なのか? そもそも、これを開発した Microsoft 社員は誰なのか? が非常に興味があるとこです。

ちなみに、ASP.NET Core MVC の VB テンプレートは入っていないので、これは自分で作らないといけません。むしろ、こっちのほうを MS さんは作って欲しかった !!!

カテゴリー: 開発 | Visual Studio 2026 と逆引きシリーズの関係 はコメントを受け付けていません

Z 世代が思う「人格まで否定されたと感じる経験」とは?

微妙にネタツイっぽい気がするのだが、レスのほうに「人格まで否定されたと感じる経験」がよくわからないという意見が多かったので、その部分だけ補足しておきます。

Z世代つまり2000年以降に生まれた世代が 25 歳になったわけで、ぼちぼちと就職してきます。大卒であれば 22 歳ぐらなので、ここ3年位から続いている訳です。なにも2000年からいきなり人類が切り替わったわけではないので幅があります。教育課程でいえば実はZ世代は、いわゆる「ゆとり世代」の後にくる世代になります。当時ゆとり教育が問題になって、円周率は3で計算するというのが話題になりました。小中学校での授業時間も減り、大学入学時までに習得する学習過程も大幅に減ってきたところです。しかし、学力低下がみられるところから一変して「ゆとり教育」を廃止して、授業数が多くなったのが Z 世代です。最近では土曜授業も月に1,2回行われるようになって、授業数は増えています。平日の授業も6,7 時間と増えているところもあります。

この平日の授業時間の増加は、昭和の世代からみても多いものです。これはゆとり教育の反省というよりも、現在義務教育で教えなければいけない要素が多くなってしまったということでしょう。様々な道徳教育や情報、理科にしても社会にしても以前よりも数多くの事実が体系化されるようになりました。中学校の教科書もページ数が多く、さらに副読本もついているので実に大変です。詰め込み部分も多くみられます。

そんななかで、大学の一芸入試や OA 入試とよばれる推薦入試が一般化してきました。これは高校入試でも同じで、中学のときの内申書(いわゆる五段階評価)に加えて技術家庭などの副教科を加えた内申の点数で推薦先を決めることが多いです。昭和世代だと、推薦入試なんてそう多くない人数だったと思うのですが(これは、出身中学や高校によるのでしょうが)、いまの中学校では、1/3 程度が推薦入試を受けます。つまり、試験一発で受かる一般入試は 2/3 程度の中学生しかいないということです。
推薦入試は、高校からすれば、素行の悪い学生を排除することができる良い制度ではありますが、推薦する人数が 1/3 ともなると、学力もたいしたことがないことが伺えます。平均よりもちょっと上程度の位なのですから、昭和世代のクラスで1人か2人程度の推薦枠どころではありません。

そうなると、先の内申書が問題になってきます。
本来ならば、内申書というのはある程度クリアできていればよいもの(極端に成績が悪い人を落とす)程度のものだったはずが、推薦入試(東京都では併願確約というのもあります)を通すために、普段の授業の点数を上げることに必死になってしまうのです。ただし、必死になるのは生徒ではなくて、教師側のほうです。
教師が生徒たちの内申をつけるために、授業の手を上げる回数だとか、グループ発表の点数だとか、遅刻の数だとか、クラブ活動の態度だとか、諸々の基準で数値化していきます。この評価が実に大変で、授業ごとに生徒が手をあげた回数&発表した回数を数えないといけないし、答える人も順番にあてないといけません(平均的に当てないと、親からクレームが来ます)。昭和の頃にも、教師が「内申をさげるぞ」と脅すことがたびたびあったのですが、それは推薦の子に限って。一般入試の子にはさほど響きません。しかし、現状のように 1/3 もの推薦(落ちる子も含めれば 1/2 に近い形で推薦入試を受けます)枠になってしまうと、「内申が落ちる」という脅しはかなりの人数で効いてきてしまうのです。

小学校のころに運動会で平等にとか学級会の話し合いでとかいう次の中学校でこんな具合なわけですから、それぞれの「人格」がどうというものは消え去ってしまいます。
もちろん、建前上、「個性」を重んじるようには見えますが、結局のところ推薦入試のために偏った細かすぎる評価基準のためにそれぞれの「個性」は見えない状態になり形骸化してしまいます。
それもあって、不登校の子も多いのです。クラスに1,2人は必ずいるという状態です。

そのような意味で「人格まで否定されて」という雰囲気が中学生時代にあり、それを経て高校・大学その後に就職というのが現在の Z 世代の現状です。否定されるというか、無視されてというか実に「個性」と「評価基準」との間を行き来しながら今に至っていると考えられます。
なので、最近の Z 世代は相手に対して実にきれいな敬語をつかいます。これは上司とかではなく、同期であっても敬語を使うし、最初からフレンドリーということはありません。勿論、新人教育の中で少しずつ慣れてくるのですが、そのあたりの「きれいな敬語」(私からみると場をわきまえない、TPOにそぐわない変な敬語)を使うのは Z 世代特有の、相手を傷つけない争いを起こさない、という下地あるいは防御本能から来るものと考えられます。

で、元ネタの「構造~」なんとかはさておき(ネタっぽいし)、この場合「遅刻前には連絡してね」で十分です。Z 世代は先に話したようにルールに厳格な人が多いので(まあ、その後の高校によったり、性格によるものがあるでしょうが)、IT に限って言えば、生真面目過ぎるのが難点なぐらいです。

サイトを見るとわかりますが、アフリサイトなので御察しですね。


カテゴリー: 開発 | Z 世代が思う「人格まで否定されたと感じる経験」とは? はコメントを受け付けていません