AI がコードを書いてくれる時代になりました。
ChatGPT や GitHub Copilot を使えば、自然言語で指示するだけでプログラムが生成されます。
それなら、もうプログラミング言語を学ぶ必要はないのでしょうか。
私は最近、この問いについて考え直しました1。
結論から言えば、プログラミング言語を学ぶ意義は今も変わっていません。
ただしその理由は、コードを書くためでも、AI の出力をチェックするためでもありません。
思考するためです。
1. 形式言語としての強み
AI がコードを書いてくれる今、プログラミング言語を学ぶ必要はあるのでしょうか。
答えは明確です。
必要です。
ただしその理由は、AI が書いたコードをチェックするためではありません。
自分が思考するためです。
数学を学ぶとき、記号を使って思考します。
微分積分を自然言語だけで理解するのは困難です。
dy/dx、∫、Σ といった記号があるから、複雑な概念を扱えます。
微分を自然言語で説明するより、dy/dx と書いた方が明快です。
記号による思考が可能になります。
ベクトルの内積を言葉で説明するのは大変ですが、
a · b = |a||b| cos θ
と書けば一行に収まります。
自然言語には行間があります。
文脈に依存します。
同じ言葉でも状況によって意味が変わります。
それは人間同士のコミュニケーションでは便利ですが、厳密な思考には向きません。
複雑な構造を扱うとき、形式言語の力が発揮されます。
形式言語とは、曖昧さを排除するために厳密に定義された言語のことを指します。
プログラミング言語も同じです。
抽象的な概念を具体的なコードで表現することで、理解が深まります。
問題を理解するとき、プログラミング言語という形式言語を使うと、曖昧さが排除されます。
論理の飛躍が見えます。
矛盾が明らかになります。
2. プログラミング言語で初めて理解できたこと
私が最初に学んだプログラミング言語は C++、Ruby、Java でした。
その後、Python、JavaScript、TypeScript、Rust と触れる言語を増やしていきました。
振り返ると、新しい言語を学ぶたびに理解が広がっていった実感があります。
プログラミング言語を学ぶことで、多くの基本概念が身につきました。
データ構造では、配列、リスト、スタック、キュー、木構造、ハッシュテーブル。
アルゴリズムでは、探索、ソート、再帰、動的計画法。
これらを自然言語だけで理解するのは難しかったと思います。
2.1. ポインタと配列
たとえば、C 言語でポインタを学んだときも同じでした。array[i] という一行は、インデックスによる直接アクセスという仕組みを端的に示します。
int value = 42;
int *ptr = &value; // アドレスを取得
*ptr = 100; // 参照先を変更Code language: JavaScript (javascript)
*ptr と &value という記号は、参照の方向性を視覚的に表現しています。
実体と参照の違い、メモリ上のアドレスという概念。
これらを自然言語だけで理解するのは困難でした。
ポインタのコードを実際に書いて動かして、初めて腑に落ちました。
2.2. 関数型プログラミング
関数型プログラミングに触れたときは、さらに世界が広がりました。
例えば、JavaScript のラムダ式を見ると、関数も値として扱えるという発見があります。
const add = (x) => (y) => x + y;
const add5 = add(5);
console.log(add5(3)); // => 8Code language: JavaScript (javascript)
関数がファーストクラスオブジェクト、つまり他の値と同じように扱える存在であること。
高階関数という、関数を受け取ったり返したりする関数。
カリー化によって部分適用ができること。
これらは自然言語で説明されても、コードを書いて動かすまで本当の理解には至りませんでした。
2.3. 再帰構造
再帰を考えてみます。
再帰とは、関数が自分自身を呼び出す仕組みです。
二分探索木を実装したときも同じでした。
二分探索木とは、左の子が親より小さく、右の子が親より大きいという規則を持つ木構造です。
class Node
attr_accessor :value, :left, :right
def initialize(value)
@value = value
@left = nil
@right = nil
end
end
class BinarySearchTree
def initialize
@root = nil
end
def insert(value)
@root = insert_node(@root, value)
end
private
def insert_node(node, value)
return Node.new(value) if node.nil?
if value < node.value
node.left = insert_node(node.left, value)
elsif value > node.value
node.right = insert_node(node.right, value)
end
node
end
endCode language: CSS (css)
木構造。
左部分木と右部分木。
再帰的な挿入。
これらの概念は、コードを書いて実装することで初めて本当に理解できました。
図や説明文だけでは、実感が伴いませんでした。
3. AI 時代でも変わらないこと
ここまで書いてきて、ひとつのことに気づきます。
プログラミング言語を学ぶ理由は、コンピュータに命令するためではありません。
自分が考えるためです。
これは「コードを書くために考える」のではありません。
「コードを書くことで考える」のです。
3.1. コードを書くことで考える
AI に「ユーザー認証機能を実装して」と指示すれば、コードは生成されます。
でもそのコードが本当に必要な機能を満たしているかどうか、判断するのは人間です。
最初から完璧な要件定義があって、それをコードに落とし込むだけ、という開発は現実にはなかなか存在しません。
実際には、コードを書きながら、試しながら、動かしながら、徐々に理解が深まっていきます。
動かしてみて、うまくいかないとわかり、別の方法を試します。
そうやって何度も書き直して、最終的に純度の高いコードが残ります。
試行錯誤の過程で多くのコードを書き捨て、要件自体が明確になっていきます。
この過程を省略することはできません。
たとえば要件定義を考えてみます。
「ユーザーがログインできる機能を作る」という要件があったとします。
これを自然言語で詳細化しようとすると、どこまで詳しく書けばいいのかわかりません。
でもコードで書こうとすると、多くの疑問が浮かびます。
struct User {
id: UserId,
username: String,
password_hash: String, // ハッシュ化されたパスワード
created_at: DateTime,
}
fn login(username: &str, password: &str) -> Result<Session, LoginError> {
// ユーザーを検索
let user = find_user_by_username(username)?;
// パスワードを検証
if !verify_password(password, &user.password_hash) {
return Err(LoginError::InvalidCredentials);
}
// セッションを作成
let session = create_session(user.id)?;
Ok(session)
}Code language: JavaScript (javascript)
コードを書こうとすると、具体的な問いが生まれます。
パスワードはどう保存するのか。
ハッシュ化は必要か。
セッションはどう管理するのか。
エラーハンドリングはどうするのか。
自然言語で「ログイン機能」と言っているだけでは、これらの問いは生まれません。
コードという形式言語を使うことで、考えるべきことが見えてきます。
3.2. コードは思考の道具
新しいものを生み出すとき、コードを書く営みは思考そのものです。
この感覚を持っている人と持っていない人では、AI をどう使うかも変わってくるでしょう。
道具を使いこなすには、道具を理解する必要があります。
そして理解するには、自分で使ってみる必要があります。
プログラミング言語を学ぶことは、今も意味があります。
それは思考の幅を広げ、問題を見る目を養い、新しい何かを生み出す力になるからです。
- -杉本啓さん: 「コードはAIが書いてくれるから、人間は要件定義にシフトすればよいとか言っているひとは、本当に新しいものをつくる上で、コードを書く営為がどれほど重要か、まるきりわかっていないのだと思う。 コードは、まずもって思考のツールなんだ。」 / X