Edited at

プログラミング言語の習得に必要なもの


はじめに

先日、エンジニアの能力と今どきの難しさというタイトルの記事(2018年)を読んで、「これはほんとにその通り」と思う一方で、具体例がないためにピンと来ない人や、マウント取りではという意見も多数見られた。というわけで、自分が比較的得意な、プログラミング言語の構文解析といった分野に関して、この記事の言わんとしていることを補足するような記事を書こうと思い至った。

記事中では、エンジニアに必要な知識や経験を、「ベース」「カテゴリ」「実行環境」という形(以下)に分けて論じている。


①ベース

コンピュータサイエンス(CS)などの理論的なもの

低レイヤー

②カテゴリ

フロントエンド / バックエンド / クライアントアプリなど

③実行環境

特定のプログラミング言語や開発環境やツール、フレームワークやライブラリなど


この中で、特に印象的であり、かつ「よくわかる」と思ったのは以下の記述だ。


③は比較的習得が楽なこともあって、初心者がプログラミングを始める際には一番コストパフォーマンスが高い。

中身はブラックボックスであってもなんとなく動くものは作れるので、自己満足にしろ仕事にしろ成果として見えるものにはなる。

ただし、流行り廃りが速く、手を動かし続けないとキャッチアップしていけない。

①は習得するのに時間かかる。その分、ここがしっかりしていると、より上位のレイヤーは比較的楽に習得できる。


これは、基礎の部分をしっかりやらないと、上っ面の部分をいくらやってもダメだ、という説教めいた話にも読めるので、反発する人もいるのがわかる一方で、経験的にはまさにそうとしか言えない、とも思う。

ここからが本題なのだが、「プログラミング言語の習得」ということに関して、この①の部分を習得しているかどうかで、明らかに差が出る、という話をしたい。あらかじめ言っておくが、このことをもってマウントを取るつもりはない(むしろ、この話は、分野が変わると自分にとっても痛いところだと言ってもいい)。


プログラミング言語の習得コスト

世の中では、「プログラミング言語の習得コスト」というのがよく問題になっているようだ。たとえば、GoやPythonは習得コストが低い、ScalaやRustは習得コストが高い、Javaは習得コストが低い(?)といった言説だ。どの言語の習得コストが低いと感じるかは人それぞれだと思うが、こういう言説を見るたびにとても違和感を感じていた。一言でいうと、「プログラミング言語自体の習得コストって問題にするほど大きなものか?」という疑問だ。

何をもってプログラミング言語を習得したといえるのかは難しいところだが、その言語を使って自分で好きなCLIアプリケーションを作れるようになったら習得したとひとまず定義する(現実的には、その言語のライブラリやフレームワーク、ビルドシステムの習得コストや業務知識の有無など様々な要素が関係してくるが、ひとまず除外する)。

そういう定義でいうのなら、自分は、2011以降くらい、プログラミング言語自体の習得にあまり時間を割いたことがない。Haskellなどの「難しい」と言われがちなプログラミング言語ですらそうだ(CoqやAgdaとかは普通のプログラミング言語ででてこない依存型や形式的な証明の話が出てくるので、別の難しさがある)。具体的な数値は難しいのだけど、個々の言語自体の習得はだいたい数日あればできているという実感がある(この自体というのがポイント。ライブラリの習得コストその他は別に計上している)。

では、なぜそこまで習得にコストがかからなかったのかというと、自分に具象構文や構文解析の分野に関する知見があり(この分野に関してはさすがに専門ではある)、型システムに関して(専門家から比べれば初学者レベルとはいえ)知っており、意味論や評価規則に関しても(専門家から比べれば初学者レベルとはいえ)知っているということが大きく影響している。


具象構文と抽象構文と型システムと意味論

先に結論だけ書くと、自分は、プログラミング言語を学ぶときに、その言語の具象構文、抽象構文、型システム、意味論、という風に分けて理解する。

具象構文、というのは、キーワードにdefを使うかfunを使うか、といった、テキストレベルでプログラムをどう表現するか、という話で、抽象構文というのは、具象構文からそういう、テキストレベル表現を除いた部分(プログラムを解釈するときに本質的に必要な部分)を指す。型システムは、型なし言語の場合は関係ないが、式(項)がどう型付けされるかに関する規則群だ。意味論は、抽象構文で表現されたプログラムがどう解釈(実行と言い換えてもいい)されるか、という事に関する話だ。

Scalaを具体例にして、これをもう少し掘り下げてみる。たとえば、Scalaで以下のようなメソッド定義

def add(x: Int, y: Int): Int = x + y

があったとして、これは、具象構文

MethodDefinition ::= "def" Name "(" Paramns ")" "=" Expression

にマッチするかどうかという視点で考えるし、これを抽象構文で表現すると、

MethodDefinition = (Name, Params, Expression)

となると考える(これらはかなり簡略化した形であることに注意)。

型システムのレベルとしては、Int型には+演算子が定義されていて、その返り値はIntである、とか、IntIntにマッチする、という風にとらえる。

意味論のレベルでは、

add(3, 4)

3 + 4

になって、

7

になるという風に考える(色々雑な表現になっているので、テクニカルに細かいツッコミはご容赦)。

このように考えるとき、プログラミング言語の習得でつまづいたポイントは、たとえば、Scalaなら、Scalaの具象構文がわかりにくかったとか、Scalaの型システムがわかりにくかったとか、Scalaの意味論がわかりにくかった、という話になる。で、Scalaの具象構文がわかりにくいかというと、まあ言語仕様読めばわかるし、Scalaの型システムがわかりにくいかというとまあわかりにくいのだけど、そのわかりにくさが本当に出てくるケースは限定的だし、意味論に関しては特筆して変なことはない、ということになる。

身近な例としてScalaを出したが、他の言語でもだいたい似たような形で認識している。

構文についていうと、滅茶苦茶変態的な構文を持つ言語はごく一部にあるものの、実用言語でそれがあるのはPerlとかRubyくらいだし(それでも、その変態的な部分に実用上遭遇するかというとしない。なお、最近学んだ言語でいうとJuliaの構文の一部はちょっと変態的)。

型システムについていうと、OCamlのような「強い」型推論を行うプログラミング言語の場合、脳内型推論器(特に単一化に関するもの)が装備されているわけではないので、予想外の型エラーや型推論でとまどうことは結構ある。とはいえ、型システムが難しすぎて立ち止まる、ということはあんまりない(型レベルプログラミングをするようなケースになると、話は別)。

評価の話については、non-strictな言語に関しては考え方を切り替えないとうまく書けないことがあるとはいえ、現状、Haskell以外で実用である程度使われているプログラミング言語のほとんどはstrictなので、関数型プログラミング言語だから特別難しい、とかいう感想を抱くことはほぼない。ぶっちゃけ、OCamlもScalaもClojureもErlangもElixirも「手続き型プログラミング言語」だと思っている(ここでの「手続き型プログラミング言語」の意味は、副作用を持つ式を適当に挿入しても、何が起こるか普通に予想できる程度の意味。本来の「手続き型言語」の用法とは違うので注意)。

このようにして新しいプログラミング言語を学習していくと、いまどきのたいていのプログラミング言語は、数日くらいあればだいたい習得できる(習得の意味は上の方を参照)。


「言語自体」という言葉の意味

勘違いされないように再度言っておくと、あくまで言語自体の習得の話をしているのであって、それですぐ実用的なアプリケーションやライブラリを構築できるわけではない。その言語でのイディオム(その言語らしいコードの書き方)やビルドツールの使い方の学習など、周辺知識が色々必要になるからだ。また、知識だけでは実用的なアプリケーションを構築するためには不足であって、当然、手を動かすことは必要になってくる。

ただ、それをひとまずおいておくのなら、自分が、言語自体についての習得コストが高い/低い、ということが実感できたことがあまりない(もちろん、実際に問題になっているのだから、向き合う必要はあるにせよ)。ちなみに、世間的(?)には、学習コストが高いプログラミング言語を習得するまでの期間は様々だと思うのだが、自分が数日で学べることが人によっては20日とか30日とかかかっているような事例も散見される。


プログラミング言語の習得の「ベース」

話は戻るのだが、プログラミング言語の習得ということに関して、ベースにあたるのは、具象構文や抽象構文、構文解析の知識、型システムの知識、意味論に関する知識、といったものになるのだと思う。この部分がわかっていれば、個々の言語を学ぶ際に起きる問題は、構文解析の問題(あるいは具象構文の問題)とか型システムの問題とか意味論の問題という風に解釈できるので、「この言語が難しくてわからない」となりにくい。

もちろん、そういう知識をプログラマ全員が学ぶべきとは思わない(なくても、ある程度かけるようになったあとは問題にならない。また、しつこいくらいに書くが、実用的なアプリケーションを構築する技能は全く別なので)。ただ、第二言語以降を学ぶ人がつまづいているポイントの多くについて自分がつまづかない理由が何か、と考えたとき、そういう「ベース」の知識が明らかに影響している、ということだ。


おわりに

この記事では、プログラミング言語の習得という話に関して、ベースの部分を習得しているかが影響している、という話にしぼったが、それ以外の分野に関してもそういう話はおそらく山ほどあると思う。中には、自分がベースの部分を習得していないせいでうまく習熟できていない分野もある。

ただ、元記事を読んでいて思ったことなのだが「コンピュータサイエンス(CS)などの理論的なもの」「低レイヤー」とおおざっぱにくくっているが、この区分けは若干雑だ。現実的には、そういう「ベース」の部分もさらに細分化されていて、そのすべてを習得するのはかなり難しいから、ベースの内のどの部分を取りに行くかの取捨選択が必要になるのではないかと思う。

ただ、ベースの部分でも取捨選択が必要になるとはいえ、そのことを学ぶ効果はおそらくかなり大きいので、「どのベース」を取りに行くかを含めて時間を配分する必要があるのではないか、と思う。