わたしがコンピュータの言語というのを初めて学んだのがちょうど20歳のとき、 学科の計算機実習でであった。 言語はFORTRANだった。 学校の宿題でFORTRANの課題が3種類出た。 課題提出のその日までにプログラミングすべし,というお達しがあった。 ということは、春休みの間にプログラミングを机上で行い、 結果をパンチカードに穴をあけておくことだった。 私は真面目だったから事前にパンチカードを買って、 わざわざ春休みパンチ室へ行って穴開けカードを用意したのに、 回りはほとんど当日に打っていた。 バカを見た。
われわれは初心者である。 そのため、 前もって打っておこうが当日に打とうがそんなのことにはおかまいなしにエラーが出る。 いちいちパンチしなおすのが面倒だなと思っていた矢先、私の先輩である K さんが現れた。 K さんはそのとき計算機実習を担当する学科の大学院生だった。 「今はTSS というので画面からプログラムが組めるんだよ。」と教えてくれた。 さすがにプログラムまでは教えてくれなかったけれどどれほど楽になったかわからない。
この実習で思い出すのはある課題にまつわるできごとである。 こんな課題だった。
1から10までの整数 iについて、 iとiの二乗、iの三乗, iの平方根を印刷せよ。
パンチの位置を間違えたため範囲を1000から10000までとしてしまい,うんかの如きコンピュータ用紙を掃き出すことになった奴がいた。 これを見て,私はこんな間違いは絶対すまい,とは思わなかった。 むしろ,こんなになるのがいやだからプログラマにはなりたくない,と思った。この思いは数年後,自分で捨てけなければならなかった。
先に挙げた課題とは別に、こんな課題があった。
二つの多項式AとBの係数がそれぞれ配列の形で与えられている。 この多項式の積の係数を配列Cに求めよ。
これでいいかと仕上げてもっていくことにした。何人かの教官が並んでその前に生徒の列ができている。 ある列を選んで順番を待っていた。次が自分になるというときが来た。私の前の奴が私と似た解法をとっている。 ふむふむ,といって眺めていると,その指導教官がその前の奴にこう言った。
こういう解法では無駄な部分を計算することになる。領域と時間がもったいない。
私は合点した。前の奴が終わったところで,私の解法を差し出すと, 案の定「前の人と同じ間違いをしてますね。聞いていてわかりましたか?」といわれた。 私はわかっていたのだけれど,その指導教官は有名なI先生だったから話を直に聞きたく, 「すみません,わからなかったのでもう一度お願いします。」とウソをついてしまった。 後で「もったいなくない」解法をもっていったが,maxとminが複雑に入り交じったプログラムだった。 I先生は,もっとすっきりした式のはずなんだけれど、まあいいことにしましょう,といわれた。
後にKさんはI先生の弟子となり,今では押しも押されもせぬ著名なK先生になった。
一般には知られていないが,BASICやAPL,LISPの感じがする,とりつきやすい言語だった。 今でもあるのかどうかしらない。
会社に入ってから,化学のある式を解く必要があり, プログラミング言語として選んだのが Speakeasy だった。 正確にいえば、当時の勤務先には富士通の汎用機があり、 その汎用機上で動作する簡易言語が Speakeasy であった。 簡単にできるのはよかったが,やはり難しいことはできず, FORTRAN とそのライブラリを使って結果を出さざるを得なかった。 それでも,今思うと手続き型のプログラミングとは考え方が違うところがあったのだった。
化学のある式を解く仕事を終えて,次なる仕事は画像処理であった。 なんでも組み込み型の画像処理装置を開発しなければ, ということで必要になったのがC言語だった。ターゲットからして, 他に候補がなかったようだ(Basicもあったかもしれない)。とまれ, あわてて勉強を始めた。1986年のことである。参考書はK&Rの初版(邦訳)と「はじめてのC」(初版)を使った。 これぐらいしか参考書のなかった時代だった。
この言語を覚えたおかげで,今でもこうしてプログラムで飯が食えている。 ただし,自分のプログラミングの腕がどうだったかというと, 今ひとつ自信がない。本当に腕が確かかどうかは、ある仕事をした結果判断したいと考えている。 その仕事とはナイショである。
しばらくはCで画像処理のプログラムを書く時代が続いた。その後,並行してBASICで
機器の制御をしないといけないはめになった。
BASICは学生時代に馴染んではいた。物理の実験のデータの整理に使ったプログラム電卓についていた言語だったからだ。
まともにはやっていなかった。
仕方なくRS-232Cのプログラムを書いたり、簡単なGUIのプログラムを書いたりしていた。
それでも簡単な機器制御なら割り込みの書き方が容易であり、案外捨てた言語じゃないな、と親近感を覚えた。
また、円や四角をひょいひょいと書くことができたのはやはりBASICのおかげである。
しかし、昔のできたころのBASICは行列演算が一行で書けるとか、いいところがあったのに、という繰り言もある。
もちろん、TrueBasicはそのいいところをもっている。
それから、U-BASICという、数学者の木田先生が作ったすばらしい処理系に出会ったのもこのときだった。
あと、それから会社の他の連中が、BASICで最適化計算をやっていた。おまえ、計算の効率は遅いしもっとましなアルゴリズム使えよ、といいたくなるのをこらえて相談に乗っていた。
あるとき、会社の先輩が Sony のワークステーション NEWS-721を買った。その先輩はそのNEWSの上で動くGUIをX10を使って書いていた。 使いたいなとあこがれていた私に、その先輩はもう使っていいよとありがたい言葉をくださった。 早速使ってみたのがLispだった。このころのNEWSにはもれなくLispがついていたんですね。 もっとも、すでにCommon-Lispが出てきた時代だったのに、 もれなくついてきたのはFranz Lispだったから力不足ではあった。 せっかくWinstonのLISPを買ったのにその例題が実行できないのだから。 それでもいろいろと楽しませてもらった。mapcarへの驚きは今でも忘れない。
コンピュータ関連の試験をまじめに受けていた頃があった。 その中の一つの「マイコン検定試験」の1級で、 必ずPrologの問題が出されるのに気づいた。 論理型言語か、面倒だな、 とつぶやいていたがそれだけでは試験には受からない。 気を取り直してまず Prolog の本を買い、 当時所有していたコンピュータ(PC-9801 VX)で動く処理系を手に入れて、 会社の同僚にわからないところを教わるなどして勉強した。 この同僚は学生時代、 第5世代コンピュータでお手伝いをしていた有名なM先生の研究室にいたので、 Prologはお手のものだった。 何から勉強していいかわからなかったが、とにかくappendを定義できるようにした。 後藤滋樹さんの「記号処理プログラミング」のPrologの章で、 「Prologの本には必ずappendの説明が出てくる」と書いてあったからである。
かくて試験に臨んだところ、驚いた。Prologの問題を見ると、 「appendを定義し、その動きを説明せよ」というものだったからだ。 その試験には合格した。 今はもう使わないが、また使ってみたい言語の一つだ。 Makefileの書き方が一種の論理型言語だ、という意見も忘れられない。
Macintoshには驚きの連続であった。HyperCardもその一つである。
ソフトがこんなに簡単に作れていいものかという驚きだった。
住所録、蔵書データベースをHyperCardで作った。
多少は手を入れたかったのでHyperTalkのスクリプトも覚えた。
もっとも何をしたかというと、
データベースを立ち上げる度に「ドレミファソラシド」という音階を出すだけだった。
ある大量の実験結果を整理しなければならないときにあわてて覚えたのがawkだった。
多少の手間はかかったが、補って余りあるほどの結果が得られた。
本当はawkだけでは処理できずにsedの助けも借りたのだけれど
(一行が非常に長いテキストを処理する必要があった)、
とにかく結果を出すことができて安心した。
UNIXのありがたみを知ったできごととして真っ先に思い出すのがawkである。
今の職場で覚えたのがC++である。
オブジェクト指向プログラムも書ける、という位置づけの言語であるという認識である。
私はあいかわらず手続き型の考えから抜けきらない。けっきょくBetter Cというだけで使っている。
個人的にはこれからSTL(Standard Template Library)を極めてみたいと思っている。
あるとき多面体の絵をきれいに作りたい、と思い立った。絵をプログラムで記述できるとありがたい、 と思ったらPostscriptでそれができることに気づいた。少しだけ勉強してその範囲内での絵を描いた。 思っていたほど難しくはなかった。
awkより自由度の高い言語として使っていた。その分難しいプログラムになりがちだった。 今ではワンライナー以上の難しいことはあまりしていない。 ワンライナーでなければ、バイナリープログラムをいじるときである。Cより楽なことがある。
昔から名前だけ聞いていたがどんなものは皆目見当がつかなかった。 あるとき、GUIプログラムが簡単にかけるのがこれだ、ときいて飛びついた。これはいい。 あれほどMotifで苦労していたことが造作なく書ける。 こんど私が作ろうとしているあるGUIプログラムでは、 このTcl/Tkを採用する予定である。
ある作曲家の作品番号が2系統あり、相互の関連がつかないという問題に何度か出会った。 その問題を解決するために採用したのが JavaScript だった。 わたしのホームページのどこかに載せている。 他にもいろいろなことができそうである。
情報処理試験のために少しだけ CASL というのもやっていた。
Gofer の勉強をしたこともあった。
AppleScript の本も買った。今では Java にも興味がある。
昔暇だったころに Pascal や COBOL もやっておけばよかったと思うが、あとの祭りである。
Smalltalk もやってみたい。Python の処理系も入れたままでまだ手をつけていない。
そうこうしているうちに、Ruby も出てきた。
そして、必要に迫られて VBA も学んだ。
しかしもう、新しい言語を学ぼうという意欲は萎えてしまった。
これが、いちばんこわいことだ。
俺はこのように嘆いてはいるが、まだ同年代の他の奴より、 また勤務先の仕事でひいこら言っている若い奴よりは、 まだ俺のほうが新しい言語に関する好奇心があると自負している。 その自負を、なんとか形にしたいものだ。(2010-08-01)
俺はもう仕事を辞めて、新しい言語に関する興味も落ちつつあるが、 いろいろと言語を試してみたいという気持ちはまだ抱いている(2020-09-15)。
WSL は Windows Subsystem for Linux 2 を表している。
言語 | 環境、処理系 | バージョン | 参考 |
---|---|---|---|
AWK | WSL gawk | 5.1.0 | |
BASIC | WSL 十進 BASIC | ||
bash | WSL bash | 5.1.16(1) | |
C | WSL clang | 13.0.0 | swift にインストールされている |
C | Visual Studio 2022 | 19.36.32534 | C++ も兼ねる。Developper Command Prompt より cl で起動する |
C++ | WSL g++ | 11.3.0 | |
C# | Visual Studio 2022 | ||
Common Lisp | Steel Bank Common Lisp | roswell 20.06.14.107(b22041e) | |
Dart | Windows11 | 2.19.6 | |
Elixir | WSL Elixir | 1.12.2 | Erlang と同時に更新、2023-07-06 |
Erlang | WSL Erlang | OTP 24 [erts-12.2.1] | |
Forth | WSL gforth | 0.7.3 | |
Fortran | WSL gfortran | 11.4.0 | $ sudo apt install gfortran ( |
F# | WSL F# 4.0 (Open Source Edition) | ||
Go | WSL Go | 1.13.8 | |
Go | Windows 11 | 1.24.2 | C:\Program Files\Go |
Haskell | WSL GHC | 8.8.4 | stack のバージョンは 2.3.3 |
Java | Windows 11 | 19.0.2 | |
JavaScript | Windows 11 Node.js | 18.12.1 | |
Julia | Windows 11 | 1.8.2 | |
Kotlin | WSL | 1.4.10 | |
OCaml | |||
Perl | XAMPP | 5.32.1 | |
PHP | XAMPP | 8.2.4 | |
Prolog | Windows 11 SWI-Prolog | 9.0.3 | |
Python | Anaconda | 3.9, 3.10 | |
R | Windows R | 4.4.1 | |
Ruby | RubyInstaller(Windows 11) | 3.1.1p18 | |
Rust | WSL Rust | 1.46.0 | |
Scala | Windows 11 | 3.2.2 | |
Tcl | WSL tcl | 8.6.1 | |
Swift | WSL | 5.7.2 | |
Smalltalk | Windows 11 Pharo | 10.0 | |
Yacc | WSL | 3.8.2 | 実際には bison |
Zig | Windows 11 | 0.11.0 |
ライブラリ | 環境 | ||
---|---|---|---|
Curl | C:\ProgramFiles\cURL |
いずれは、 世界のプログラミング言語(new.mynavi.jp)で取り上げられた言語をすべて網羅したい。 また、AtCoder Beginners Selection(atcoder.jp) にある言語も対象としたい。 ほかにも、まつもろゆきひろ氏による、 Rubyist のための他言語探訪(magazine.rubyist.net) にある言語にもそそられる。
言語による構文の違いは下記を参考にした。
言語 | 変数宣言 | 選択 | 前反復 |
---|---|---|---|
AWK | i = 0 s = "abc" |
if (condition) {A} else {B} | while (condition) {A} |
C | int i;double f; | if (condition) {A}else{B} | while (condition){A} |
Fortran | integer i | if (condition) then
A else B end if |
do while (condition) A end do |
Go | var i int var f float64 |
if condition {A}else{B} |
for ;condition; {A} |
JavaScript | let i = 0 let s = "abc" |
if (condition) {A} else {B} | while (condition) {A} |
平方数の和という古典的な問題を題材として、プログラミング言語を考える。 この問題とプログラムの作成法は二村良彦による「プログラム技法」(以下「技法」) からの引用である。 なお「技法」では、「ここで述べる方法は Wirth, N の「系統的プログラミング入門」(近代科学社)による」 と明記しているので、私のはいわゆる孫引きにあたるが、勘弁してほしい。
「与えられた自然数 N を超えない範囲で,二つの平方数の和,すなわち i 2 + j 2 を小さい順に( i および j と共に) 印字する(ただし, i ,j ≧ 1 .また i と j が違えば i 2 + j 2 が同じ場合でも両方とも印字する)」 というプログラムの作り方が以下に示してある.(後略)
以下のプログラムでは、N = 1000 と決め打ちしている。 また、Sij = i2 + j2 とおくと、 Sij = Sji であるから、 i ≧ j の場合だけ考えればよいので、解答例ではこの場合のみを記している。 なお、「技法」では、 平方数を都度作らず、配列としてメモ化することで計算の回数を減らす工夫を行うような課題を提案しているが、 この対応はしていない。
解答例は次の通りである。なお、この問題は「ピタゴラス数の三つ組」と関連が深い。 (http://rosettacode.org/wiki/Pythagorean_triples)
S i j ----- 2 1 1 5 2 1 8 2 2 10 3 1 13 3 2 17 4 1 18 3 3 20 4 2 25 4 3 26 5 1 29 5 2 32 4 4 34 5 3 37 6 1 40 6 2 41 5 4 45 6 3 50 5 5 50 7 1 52 6 4 53 7 2 58 7 3 61 6 5 65 7 4 65 8 1 68 8 2 72 6 6 73 8 3 74 7 5 80 8 4 82 9 1 85 7 6 85 9 2 89 8 5 90 9 3 97 9 4 98 7 7 100 8 6 101 10 1 104 10 2 106 9 5 109 10 3 113 8 7 116 10 4 117 9 6 122 11 1 125 10 5 125 11 2 128 8 8 130 9 7 130 11 3 136 10 6 137 11 4 145 9 8 145 12 1 146 11 5 148 12 2 149 10 7 153 12 3 157 11 6 160 12 4 162 9 9 164 10 8 169 12 5 170 11 7 170 13 1 173 13 2 178 13 3 180 12 6 181 10 9 185 11 8 185 13 4 193 12 7 194 13 5 197 14 1 200 10 10 200 14 2 202 11 9 205 13 6 205 14 3 208 12 8 212 14 4 218 13 7 221 11 10 221 14 5 225 12 9 226 15 1 229 15 2 232 14 6 233 13 8 234 15 3 241 15 4 242 11 11 244 12 10 245 14 7 250 13 9 250 15 5 257 16 1 260 14 8 260 16 2 261 15 6 265 12 11 265 16 3 269 13 10 272 16 4 274 15 7 277 14 9 281 16 5 288 12 12 289 15 8 290 13 11 290 17 1 292 16 6 293 17 2 296 14 10 298 17 3 305 16 7 305 17 4 306 15 9 313 13 12 314 17 5 317 14 11 320 16 8 325 15 10 325 17 6 325 18 1 328 18 2 333 18 3 337 16 9 338 13 13 338 17 7 340 14 12 340 18 4 346 15 11 349 18 5 353 17 8 356 16 10 360 18 6 362 19 1 365 14 13 365 19 2 369 15 12 370 17 9 370 19 3 373 18 7 377 16 11 377 19 4 386 19 5 388 18 8 389 17 10 392 14 14 394 15 13 397 19 6 400 16 12 401 20 1 404 20 2 405 18 9 409 20 3 410 17 11 410 19 7 416 20 4 421 15 14 424 18 10 425 16 13 425 19 8 425 20 5 433 17 12 436 20 6 442 19 9 442 21 1 445 18 11 445 21 2 449 20 7 450 15 15 450 21 3 452 16 14 457 21 4 458 17 13 461 19 10 464 20 8 466 21 5 468 18 12 477 21 6 481 16 15 481 20 9 482 19 11 485 17 14 485 22 1 488 22 2 490 21 7 493 18 13 493 22 3 500 20 10 500 22 4 505 19 12 505 21 8 509 22 5 512 16 16 514 17 15 520 18 14 520 22 6 521 20 11 522 21 9 530 19 13 530 23 1 533 22 7 533 23 2 538 23 3 541 21 10 544 20 12 545 17 16 545 23 4 548 22 8 549 18 15 554 23 5 557 19 14 562 21 11 565 22 9 565 23 6 569 20 13 577 24 1 578 17 17 578 23 7 580 18 16 580 24 2 584 22 10 585 21 12 585 24 3 586 19 15 592 24 4 593 23 8 596 20 14 601 24 5 605 22 11 610 21 13 610 23 9 612 24 6 613 18 17 617 19 16 625 20 15 625 24 7 626 25 1 628 22 12 629 23 10 629 25 2 634 25 3 637 21 14 640 24 8 641 25 4 648 18 18 650 19 17 650 23 11 650 25 5 653 22 13 656 20 16 657 24 9 661 25 6 666 21 15 673 23 12 674 25 7 676 24 10 677 26 1 680 22 14 680 26 2 685 19 18 685 26 3 689 20 17 689 25 8 692 26 4 697 21 16 697 24 11 698 23 13 701 26 5 706 25 9 709 22 15 712 26 6 720 24 12 722 19 19 724 20 18 725 23 14 725 25 10 725 26 7 730 21 17 730 27 1 733 27 2 738 27 3 740 22 16 740 26 8 745 24 13 745 27 4 746 25 11 754 23 15 754 27 5 757 26 9 761 20 19 765 21 18 765 27 6 769 25 12 772 24 14 773 22 17 776 26 10 778 27 7 785 23 16 785 28 1 788 28 2 793 27 8 793 28 3 794 25 13 797 26 11 800 20 20 800 28 4 801 24 15 802 21 19 808 22 18 809 28 5 810 27 9 818 23 17 820 26 12 820 28 6 821 25 14 829 27 10 832 24 16 833 28 7 841 21 20 842 29 1 845 22 19 845 26 13 845 29 2 848 28 8 850 25 15 850 27 11 850 29 3 853 23 18 857 29 4 865 24 17 865 28 9 866 29 5 872 26 14 873 27 12 877 29 6 881 25 16 882 21 21 884 22 20 884 28 10 890 23 19 890 29 7 898 27 13 900 24 18 901 26 15 901 30 1 904 30 2 905 28 11 905 29 8 909 30 3 914 25 17 916 30 4 922 29 9 925 22 21 925 27 14 925 30 5 928 28 12 929 23 20 932 26 16 936 30 6 937 24 19 941 29 10 949 25 18 949 30 7 953 28 13 954 27 15 962 29 11 962 31 1 964 30 8 965 26 17 965 31 2 968 22 22 970 23 21 970 31 3 976 24 20 977 31 4 980 28 14 981 30 9 985 27 16 985 29 12 986 25 19 986 31 5 997 31 6 1000 26 18 1000 30 10
では、各言語について調べてみよう。私が一番慣れている C と JavaScript で作った後、他の言語に移植した。
C とほとんど変わらない。C から変えたのは、変数宣言を削除したこと、 ++ 演算子の代わりに明示的に 1 を加える処理にしたこと、セミコロンを削除したことぐらいだ。
tcl の真似をすれば書けるだろうと思っていたら、甘かった。 まず、= の左右に空白を置くことは許されない。また、if や while などで比較をする際には、 < や > 、<=、>= は利用できない。それぞれ、gt、lt、le、gt を使わなければならない。 さらに、C 言語のような for 文は bash では使えないと思い込んでいたので while 文に直した。 一番苦労したのは、算術式を評価するためのコマンド expr が自分のコーディングではうまく動かないことだった。 結局 expr の利用をあきらめ、bash 固有の機能である二重カッコ式 $((...)) を使って切り抜けた。
expr を使わないご利益はほかにもあって、なんといっても速度が落ちないのである。expr を使うと、 私の鈍い動体視力でもわかるぐらい、結果を出力する画面が止まって見える。
なお、C 言語のような for 文は bash でも使えることがあとでわかったが、 いじってしまったあとでは遅い。今回は while 文のバージョンで掲げる。
まず、bc について述べる。bc とは UNIX 系の OS で標準装備されている対話式プログラムであり、 また、この bc で使われる言語も指す。標準文法は C によく似ている。 私が使ったのは GNU 版で、これは標準 bc の文法から拡張されている。 たとえば、標準文法では変数は 1 文字のみであるが、GNU 版では変数は 1 文字に制限されない。 また、標準文法では if 文に else 節を書くことはできないが、GNU 版ではできる。
上記で述べた通り、bc の文法は C 言語の文法に近いので、ほとんど修正していない。 修正箇所は、文末のセミコロンの削除、print 文の修正などである。 また、j++ のように1行で ++ 演算子を用いている文は、 その行で式として評価されるために変数が表示されてしまうため、j += 1 のように変更した。 なお、bc の長所は精度が指定できることである。もちろん、時間や記憶容量などとの兼ね合いもあるが、 好きなだけの精度で計算できることは驚異である。
ほとんど「技法」の通りである。 配列 j と s のサイズを和の最大数 n と同じく 1000 としたが、これはもったいない。 おそらく j と s のサイズは ceil(sqrt(n)) + 2 で収まるはずであるがその見極めが難しかった。下手をすると、 アドレスエラーになることが目に見えていたのだ。
C から変えるべきなのは、printf 関数の代わりに iostream にある cout, cerr を使うことである。 それからもう一つは、領域の確保である。 C 言語では配列 j と配列 s を動的に確保するときに calloc を使い、 領域を解放するときに free を使っていた。 C++ に移行するだけであればそれぞれ new と delete を使えばそれでよい。 しかしそれでは能がないので、配列ではなく標準テンプレートライブラリ(STL)のコンテナである map を使うことにした。これを使って、j[low] から s[high] まで、 また s[low] から s[high] までの領域をムダなく確保することができる。 とくに、low が 1 増えるまえに、j[low] と s[low] を map 領域から erase しておくことが重要である。 こうすれば、j や s の領域をムダ遣いすることはない。このプログラムを組んで、昔 I 先生に言われた、 「こういう解法では無駄な部分を計算することになる。領域と時間がもったいない。」 というお叱りを思い出し、 やっとこの年になって「このように改善できましたよ」ということができるようになった。
Scheme でのリスト内包表記に味をしめて、 Common Lisp でも使ってみることにした。 incf-cl を使うのがミソである。 Common Lispでリスト内包表記(kikkii.hatenablog.com) を参考にした。
JavaScript のプログラムとほとんど同じである。 変更点は、文末にすべてセミコロンを入れたこと、 JavaScript の配列を Dart のリストに変えたこと、これに伴い新規にリストの要素を作るときは、 add メソッドを使ったことだ。リストの参照は配列と同じようにインデックスで可能である。
Scala 版のコードを基に Elixir 用に手直しした。
Scala 版と比較して、中間の変数を多く入れている。
これは、Haskell 版と同じ理由で、
Elixir の理解が足りないためである。
コーディングスタイルがめちゃくちゃで、カッコがあったりなかったり、
無名関数の書き方がいくつもあったりでとても見せられる物ではないが、
勘弁してもらいたい。
Common Lisp 版や Scheme 版と同様、
リスト内包表記を使っている。「プログラミング Erlang」の pp.43-44 で、
ピタゴラス数をリスト内包表記で生成している例を見て、これならできそうだと思った。
Scheme や Lisp との違いは、Erlang ではリスト内包表記が言語に組み込まれていて、
新たなライブラリやパッケージをロードする必要がないことである。
今気づいたのだが、リスト内包表記を標準で装備している Haskell や Python で、
なぜリスト内包表記を使わなかったのだろう。
詳しくはForth 特設ページを参照。
他の言語とはかなり見た目が違うので解説する。
1行めは \ から始まっている。これはコメントである。実行には、
$ gforth sqsum.fth -e bye
とすればよい。なお、Forth のソースファイルの拡張子には、f, frt, fth, fs, 4th, forth などが用いられている。
2行めは変数 p, low, high の宣言である。他の言語では i を宣言しているが、
i は Forth ではワード(予約語のようなもの)であり、再定義がこわいので p を使っていた。
変数定義で value を使うと、初期値が必要となる。初期値 value 変数名 で定義できる。
3行めは配列の宣言である。create 変数名 配列の大きさ cells allot で、
変数名に配列の大きさだけの領域を確保できる。配列名は q と s を使っている。s は多言語でも同じであるが、
他の言語の j のかわりにここでは q を使っている。これは、j も Forth のワードであるため、
バッティングを恐れたためである。
4行目は配列 q と s に初期値を格納している。2 s 1 + ! は s[1] = 2 のようなものだと思ってもらいたい。
5行目以降はワードの定義である。ワードとはサブルーチンのようなものである。: ワードの定義は : で始まり、
そのあとに続く単語がワード名である。その後のワードの定義が続き、; で終わる。
5行目のワード sq はスタックの数を二乗する。
6行目のワード sq+ はスタックにある2つ数をそれぞれ二乗して、これらの和を得る。
7行目のワード argmin は、s[low] から s[high] までの配列の値を調べ、最も小さな値 s[p] となるその p を得る。
8行目のワード shrink は、q や s の探索の下限 low を low += 1 することにより狭めている。
9行目のワード widen は、q や s の探索の上限 high を high += 1 することにより広げているとともに必要な値を埋めている。
10行目のワード heighten は q 増えたときの s の値を計算している。
11行目のワード enlarge は widen や heighten を行うときの元締めである。
12行目のワード fit は shrink か enlarge かを決める元締めである。
13行目のワード prt は s, p, q (表示は s i j )を行うワードである。
14行目のワード sqsum がメインルーチンで、表示と fit を所定の数まで行なう。
ここまででワードの定義が終り、後は sqsum を実行するだけである。
なぜ Forth のプログラミングをしようかと思ったか。私には Forth が得意な友人がいた。何かのときに、 この友人は、自分は C も使うし、周りの人も C を使うが、Forth は「はやい」のだ、と力説していた。 この「はやい」は、開発に時間がかからずに「早い」のかもしれないし、実行速度が「速い」の両方があったのではないか。 友人は早世してしまい、その真意をつかむことができなくなってしまった。追善の意味でプログラミングをしてみたが、 私にとっては恥ずかしくも「遅い」プログラミング言語となってしまった。
Fortran 95 の規格でコンパイルした($ f95 sqsum.f95)。
なお、Fortran のソースファイルの拡張子には、f90, f95, f08 などが用いられている。
文法は https://nag-j.co.jp/fortran/index.html を参考にした。
私の記憶にかすかにある、文番号を駆使した FORTRAN77 からは遠く隔たっている。
C からの移植は多少手こずったが、ほとんど左から右への書き換えで済んでいる。
ただ、「技法」でいう前反復を表す構文が上記ページで記述された文法には見当たらなかった。
なので、他のページにあった、do while 文を使っている。例:
http://maya.phys.kyushu-u.ac.jp/~knomura/education/numerical/text9/node3.html
拡張子は fs, fsi, fsx, fsscript などが用いられている。 これもリスト内包表記を用いている(個人的には、for ... in ... do という構文はリスト内包表記のような気がしない)。
上記は go fmt を通したあとのコードである。 C からほとんど変えていない。配列の初期化宣言のところ以外は、 ほとんど C のコードと同じだろう。配列のかわりにスライスかマップを使えば Go らしさが少しは出るのだろうが、私の実力ではできない。
Scala のコードを基に Haskell 用に手直しした。
Scala 版と比較して、中間の変数を多く入れている。これは、Haskell の理解が足りないためである。
Haskell と Scala は似て非なる言語だということを痛感した。
sortBy を実現するのに import Data.List が必要だということに気づくのにかなりの時間を費やした。
また、Scala の flatten に相当する関数は Haskell では concat になるということを調べるのにけっこうな時間がかかった。
おまけに、標準出力ができるのが main だけというのを理解するのにさらに時間がかかった。
Java は仕事で使ったことが全くないので、"hello, world!" さえ表示できない。 そのため苦労した。C 言語にかなり手を加えた。 HashMap の取り扱いも苦労した。他の言語であれば、配列であるかのようにアクセスできるのに、 Java は put メソッドと get メソッドでアクセスしなければならない。依怙地だ。 なお、C++ や Perl と同じように、お役御免になった HashMap の要素は remove メソッドで消している。
AWK とほとんど変わらない。 AWK から変えたのは、変数宣言 (const|let) を追記したこと、 変数 j と s を配列として初期化したことぐらいだ。
この問題はリスト内包表記を使うことで楽に解けるので、Kotlin の解法でもこれを使っている。一番参考になったのは、 List Comprehensions (rosettacode.org) だ。他の言語の解法では、リスト内包表記の map する順序を考えることで和の昇順に並べられ、 その結果 sort が不要にすることができたのだが、この Kotlin の Map と flatMap、filter の入れ子では、 和の昇順に並べるプログラムができなかった。恥ずかしいが、Scheme と同じように、まずは該当する組をすべて求めたあと、 三つ組で和を表す最初の要素をキーにして昇順にソーティングしている。
AWK 版を元にしている。 Perl でプログラムを組むのは 10 年ぶりだ。変数に $ をつける、文末に C と同じように ; をつける、 ということすら忘れてしまっていた。 変数 j と s はハッシュで作っている。C++ と同様に、お役御免になったハッシュは delete で消している。
JavaScript とほとんど変わらない。変数 j と s をリストで表現して、 新たに追加するときは append メソッドを使っているのが Pytyon らしいところだろうか。
リスト内包表記を使うと、1行で書ける。ただし改行は省略している。 可読性は落ちるが、こんな書き方もできるという見本である。
n=1000;print([(s,i,j) for s in range(1,n+1) for i in range(1,round(n**0.5+0.5)) for j in range(1,i+1) if i**2 + j**2 = = s])
この解法は、リスト内包表記でピタゴラス数を求めている解法に基づく。Python によるピタゴラス数生成は、
List comprehension for Pythagorean Triples
(stackoverflow.com) に掲載されている。
ここで一番わかりにくいのが、リスト内包表記とは関係ない、i のレンジの上端 round(n**0.5+0.5) の意味だろう。
ご存じの通り、Python には小数点を四捨五入する関数 round は組み込まれているが、
小数点以下を切り上げる ceil 関数を使うには math モジュールをインポートしないといけない。
ついでに、平方根をとる関数 sqrt も math モジュールに含まれている。ここで行いたかったことは、
ceil(sqrt(n)) なのであるが、math モジュールのインポートを嫌ったためにこのようなことになっている。
まず、sqrt(n) は n の 0.5 乗だから n**0.5 となる。また、ceil(a) = round(a + 0.5)と考えていいので、
0.5 のゲタをはかせて四捨五入している。
この四捨五入に関して、round は次のような挙動を示す。
round(0.5) # → 0 round(1.5) # → 2 round(2.5) # → 2 round(3.5) # → 4
四捨五入なのに、なぜ round(0.5)が1 にならないのか、不思議に思う人がいるかもしれない。 これは JIS Z 8401 で定められた方式である。 この方式によっても、リスト内包表記による1行アルゴリズムは正しい値を出す。 ただ、こんな際どい橋をわたるのならば、import mathをしてceilを使うべきだったのかもしれない。
R 言語初心者の私なのでヘボなプログラムである。全体としては Scala のアプローチに近いが、 map 関数や zip 関数などは使っていない。 骨子は、二つのユーザ定義関数 inits と tails にある。inits は、 ベクトル(a,b,c,d,e)をベクトル(a,b,c,d,e,a,b,c,d,a,b,c,a,b,a) に変換する関数で、 tails はベクトル(a,b,c,d,e)を(a,b,c,d,e,b,c,d,e,c,d,e,d,e,d) に変換する関数である。 これらの再帰を使った関数をもとに平方数の和を計算して、ソーティングしたり抽出したりしている。
C 言語からほとんど変わりがない。
目につく変更は、j や s を配列からベクタにしたことである。
単にベクタにしただけでは、実行時に次のパニックが生じる。
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 3',
src/main.rs:26:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
これは、high += 1; した後で j や s に新しい値を入れる個所でのエラーを表している。
今までの C のコードでは
j[high] = 1;
s[high] = high * high + 1;
でよかったところ、Rust では新たなインデックスでアクセスするため、
「境界を超えた」というエラーになる。そのため、このように書き換えなければならない。
j.push(1)
s.push(high * high + 1);
この2個所の書き換えだけで済んだのは幸運だった。
他の書き換えは、for のループの書き方、for や if のカッコの除去、++ 演算子の += 1 への置き換えだが、 特筆すべき点はない。
Scala 初心者の私なのでヘボなプログラムであるが、ループ変数がないことに注目してもらいたい。 考え方は Scala のページに記した。
Scala や Haskell など、ほかの関数型言語の真似をしようとして書こうとしたが、どうしてもうまくいかない。 何のために関数型言語を学んだのだろうと落胆して、Scheme や Scheme の実装である Gauche についてあれこれ調べていた。 すると、「リスト内包表記」というキーワードが目に留まり、それからいろいろ調べた。 その結果、リスト内包表記をサポートした srfi-42 を使えば、すっきりしたプログラムになることがわかった。 結局は、私がプログラムした部分はほとんどなく、 Gauche ユーザリファレンス:先行評価的内包表記 (practical-scheme.net)にあるタクシー数の生成例や、 マクロの効用 (practical-scheme.net) にある「リストの内包表記 (List comprehension)」で示されたピタゴラス数の生成例(pyth n) とほとんど変わらない。私がしたことは、上限の二乗和から平方根をとって個別の数の上限にしたことと、 二乗和に関してソーティングしたことだけである。なお、 三つ組ごとに改行を入れる処理はしなかった。
Tcl とはアプリケーション組み込み用言語として設計された小さな言語。 Tcl の有用性を示すために付属としてつけた Tk が GUI 作成にうってつけであったため、 Tcl/Tk という形で世の中に広まった。今回は Tcl のみで書いている。
変数は、参照時には $ をつけて、代入の左辺値となるときには $ をつけない。これに注意して、 perl のソースコードを基にして改造した。if や while 、for の条件節などでは () ではなく、{} を使うことに注意する。 また、代入時に式を評価するときには都度 expr をつけなければならない。これで苦労した。
なお、WSL2 で tcl を起動するには、
$ tclsh
とする。(2023-04-27)
まりんきょ学問所 > コンピュータの部屋 > コンピュータ言語遍歴