コンピュータ言語遍歴

作成日 : 1998-07-14
最終更新日 :

新着情報

FORTRAN

わたしがコンピュータの言語というのを初めて学んだのがちょうど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先生になった。

SPEAKEASY

一般には知られていないが,BASICやAPL,LISPの感じがする,とりつきやすい言語だった。 今でもあるのかどうかしらない。

会社に入ってから,化学のある式を解く必要があり, プログラミング言語として選んだのが Speakeasy だった。 正確にいえば、当時の勤務先には富士通の汎用機があり、 その汎用機上で動作する簡易言語が Speakeasy であった。 簡単にできるのはよかったが,やはり難しいことはできず, FORTRAN とそのライブラリを使って結果を出さざるを得なかった。 それでも,今思うと手続き型のプログラミングとは考え方が違うところがあったのだった。

C

化学のある式を解く仕事を終えて,次なる仕事は画像処理であった。 なんでも組み込み型の画像処理装置を開発しなければ, ということで必要になったのがC言語だった。ターゲットからして, 他に候補がなかったようだ(Basicもあったかもしれない)。とまれ, あわてて勉強を始めた。1986年のことである。参考書はK&Rの初版(邦訳)と「はじめてのC」(初版)を使った。 これぐらいしか参考書のなかった時代だった。

この言語を覚えたおかげで,今でもこうしてプログラムで飯が食えている。 ただし,自分のプログラミングの腕がどうだったかというと, 今ひとつ自信がない。本当に腕が確かかどうかは、ある仕事をした結果判断したいと考えている。 その仕事とはナイショである。

BASIC

しばらくはCで画像処理のプログラムを書く時代が続いた。その後,並行してBASICで 機器の制御をしないといけないはめになった。 BASICは学生時代に馴染んではいた。物理の実験のデータの整理に使ったプログラム電卓についていた言語だったからだ。 まともにはやっていなかった。
仕方なくRS-232Cのプログラムを書いたり、簡単なGUIのプログラムを書いたりしていた。 それでも簡単な機器制御なら割り込みの書き方が容易であり、案外捨てた言語じゃないな、と親近感を覚えた。 また、円や四角をひょいひょいと書くことができたのはやはりBASICのおかげである。
しかし、昔のできたころのBASICは行列演算が一行で書けるとか、いいところがあったのに、という繰り言もある。 もちろん、TrueBasicはそのいいところをもっている。
それから、U-BASICという、数学者の木田先生が作ったすばらしい処理系に出会ったのもこのときだった。
あと、それから会社の他の連中が、BASICで最適化計算をやっていた。おまえ、計算の効率は遅いしもっとましなアルゴリズム使えよ、といいたくなるのをこらえて相談に乗っていた。

Lisp

あるとき、会社の先輩が Sony のワークステーション NEWS-721を買った。その先輩はそのNEWSの上で動くGUIをX10を使って書いていた。 使いたいなとあこがれていた私に、その先輩はもう使っていいよとありがたい言葉をくださった。 早速使ってみたのがLispだった。このころのNEWSにはもれなくLispがついていたんですね。 もっとも、すでにCommon-Lispが出てきた時代だったのに、 もれなくついてきたのはFranz Lispだったから力不足ではあった。 せっかくWinstonのLISPを買ったのにその例題が実行できないのだから。 それでもいろいろと楽しませてもらった。mapcarへの驚きは今でも忘れない。

Prolog

コンピュータ関連の試験をまじめに受けていた頃があった。 その中の一つの「マイコン検定試験」の1級で、 必ずPrologの問題が出されるのに気づいた。 論理型言語か、面倒だな、 とつぶやいていたがそれだけでは試験には受からない。 気を取り直してまず Prolog の本を買い、 当時所有していたコンピュータ(PC-9801 VX)で動く処理系を手に入れて、 会社の同僚にわからないところを教わるなどして勉強した。 この同僚は学生時代、 第5世代コンピュータでお手伝いをしていた有名なM先生の研究室にいたので、 Prologはお手のものだった。 何から勉強していいかわからなかったが、とにかくappendを定義できるようにした。 後藤滋樹さんの「記号処理プログラミング」のPrologの章で、 「Prologの本には必ずappendの説明が出てくる」と書いてあったからである。

かくて試験に臨んだところ、驚いた。Prologの問題を見ると、 「appendを定義し、その動きを説明せよ」というものだったからだ。 その試験には合格した。 今はもう使わないが、また使ってみたい言語の一つだ。 Makefileの書き方が一種の論理型言語だ、という意見も忘れられない。

HyperTalk

Macintoshには驚きの連続であった。HyperCardもその一つである。 ソフトがこんなに簡単に作れていいものかという驚きだった。 住所録、蔵書データベースをHyperCardで作った。 多少は手を入れたかったのでHyperTalkのスクリプトも覚えた。 もっとも何をしたかというと、 データベースを立ち上げる度に「ドレミファソラシド」という音階を出すだけだった。

awk

ある大量の実験結果を整理しなければならないときにあわてて覚えたのがawkだった。 多少の手間はかかったが、補って余りあるほどの結果が得られた。 本当はawkだけでは処理できずにsedの助けも借りたのだけれど (一行が非常に長いテキストを処理する必要があった)、 とにかく結果を出すことができて安心した。 UNIXのありがたみを知ったできごととして真っ先に思い出すのがawkである。

C++

今の職場で覚えたのがC++である。 オブジェクト指向プログラムも書ける、という位置づけの言語であるという認識である。 私はあいかわらず手続き型の考えから抜けきらない。けっきょくBetter Cというだけで使っている。
個人的にはこれからSTL(Standard Template Library)を極めてみたいと思っている。

Postscript

あるとき多面体の絵をきれいに作りたい、と思い立った。絵をプログラムで記述できるとありがたい、 と思ったらPostscriptでそれができることに気づいた。少しだけ勉強してその範囲内での絵を描いた。 思っていたほど難しくはなかった。

Perl

awkより自由度の高い言語として使っていた。その分難しいプログラムになりがちだった。 今ではワンライナー以上の難しいことはあまりしていない。 ワンライナーでなければ、バイナリープログラムをいじるときである。Cより楽なことがある。

Tcl/Tk

昔から名前だけ聞いていたがどんなものは皆目見当がつかなかった。 あるとき、GUIプログラムが簡単にかけるのがこれだ、ときいて飛びついた。これはいい。 あれほどMotifで苦労していたことが造作なく書ける。 こんど私が作ろうとしているあるGUIプログラムでは、 このTcl/Tkを採用する予定である。

JavaScript

ある作曲家の作品番号が2系統あり、相互の関連がつかないという問題に何度か出会った。 その問題を解決するために採用したのが JavaScript だった。 わたしのホームページのどこかに載せている。 他にもいろいろなことができそうである。

その外

情報処理試験のために少しだけ CASL というのもやっていた。 Gofer の勉強をしたこともあった。 AppleScript の本も買った。今では Java にも興味がある。
昔暇だったころに Pascal や COBOL もやっておけばよかったと思うが、あとの祭りである。 Smalltalk もやってみたい。Python の処理系も入れたままでまだ手をつけていない。 そうこうしているうちに、Ruby も出てきた。 そして、必要に迫られて VBA も学んだ。 しかしもう、新しい言語を学ぼうという意欲は萎えてしまった。 これが、いちばんこわいことだ。

俺はこのように嘆いてはいるが、まだ同年代の他の奴より、 また勤務先の仕事でひいこら言っている若い奴よりは、 まだ俺のほうが新しい言語に関する好奇心があると自負している。 その自負を、なんとか形にしたいものだ。(2010-08-01)

俺はもう仕事を辞めて、新しい言語に関する興味も落ちつつあるが、 いろいろと言語を試してみたいという気持ちはまだ抱いている(2020-09-15)。

現在使用できるようにしている言語

WSL は Windows Subsystem for Linux 2 を表している。

言語環境、処理系バージョン参考
AWKWSL gawk5.1.0
BASICWSL 十進 BASIC
bashWSL bash5.1.16(1)
CWSL clang13.0.0swift にインストールされている
CVisual Studio 2022
C++WSL g++11.3.0
C#Visual Studio 2022
Common LispSteel Bank Common Lisproswell 20.06.14.107(b22041e)
DartWindows112.19.6
ElixirWSL Elixir1.12.2Erlang と同時に更新、2023-07-06
ErlangWSL ErlangOTP 24 [erts-12.2.1]
ForthWSL gforth0.7.3
FortranWSL gfortran11.2.0$ sudo apt install gfortran
F#WSL F# 4.0 (Open Source Edition)
GoWSL Go1.13.8
HaskellWSL GHC8.8.4stack のバージョンは 2.3.3
JavaWindows 1119.0.2
JavaScriptWindows 11 Node.js18.12.1
JuliaWindows 111.8.2
KotlinWSL1.4.10
OCaml
PerlXAMPP5.32.1
PHPXAMPP8.2.4
PrologWindows 11 SWI-Prolog9.0.3
PythonAnaconda3.9, 3.10
RWSL R4.0.2
RubyRubyInstaller(Windows 11)3.1.1p18
RustWSL Rust1.46.0
ScalaWindows 113.2.2
TclWSL tcl8.6.1
SwiftWSL5.7.2
SmalltalkWindows 11 Pharo10.0
YaccWSL 3.8.2実際には bison
ZigWindows 110.11.0

現在使用できるようにしているライブラリ

ライブラリ環境
CurlC:\ProgramFiles\cURL

言語のまとめ

いずれは、 世界のプログラミング言語(new.mynavi.jp)で取り上げられた言語をすべて網羅したい。 また、AtCoder Beginners Selection(atcoder.jp) にある言語も対象としたい。 ほかにも、まつもろゆきひろ氏による、 Rubyist のための他言語探訪(magazine.rubyist.net) にある言語にもそそられる。

言語による構文の違いは下記を参考にした。

言語変数宣言選択前反復
AWKi = 0
s = "abc"
if (condition) {A} else {B} while (condition) {A}
C int i;double f; if (condition) {A}else{B} while (condition){A}
Fortraninteger 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}
JavaScriptlet i = 0
let s = "abc"
if (condition) {A} else {B} while (condition) {A}

共通問題 - 平方数の和

平方数の和という古典的な問題を題材として、プログラミング言語を考える。 この問題とプログラムの作成法は二村良彦による「プログラム技法」(以下「技法」) からの引用である。 なお「技法」では、「ここで述べる方法は Wirth, N の「系統的プログラミング入門」(近代科学社)による」 と明記しているので、私のはいわゆる孫引きにあたるが、勘弁してほしい。

「与えられた自然数 N を超えない範囲で,二つの平方数の和,すなわち i 2 + j 2 を小さい順に( i および j と共に) 印字する(ただし, ij ≧ 1 .また ij が違えば i 2 + j 2 が同じ場合でも両方とも印字する)」 というプログラムの作り方が以下に示してある.(後略)

以下のプログラムでは、N = 1000 と決め打ちしている。 また、Sij = i2 + j2 とおくと、 Sij = Sji であるから、 ij の場合だけ考えればよいので、解答例ではこの場合のみを記している。 なお、「技法」では、 平方数を都度作らず、配列としてメモ化することで計算の回数を減らす工夫を行うような課題を提案しているが、 この対応はしていない。

解答例は次の通りである。なお、この問題は「ピタゴラス数の三つ組」と関連が深い。 (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 で作った後、他の言語に移植した。

AWK

C とほとんど変わらない。C から変えたのは、変数宣言を削除したこと、 ++ 演算子の代わりに明示的に 1 を加える処理にしたこと、セミコロンを削除したことぐらいだ。

bash (Bourne Again SHell)

tcl の真似をすれば書けるだろうと思っていたら、甘かった。 まず、= の左右に空白を置くことは許されない。また、if や while などで比較をする際には、 < や > 、<=、>= は利用できない。それぞれ、gt、lt、le、gt を使わなければならない。 さらに、C 言語のような for 文は bash では使えないと思い込んでいたので while 文に直した。 一番苦労したのは、算術式を評価するためのコマンド expr が自分のコーディングではうまく動かないことだった。 結局 expr の利用をあきらめ、bash 固有の機能である二重カッコ式 $((...)) を使って切り抜けた。

expr を使わないご利益はほかにもあって、なんといっても速度が落ちないのである。expr を使うと、 私の鈍い動体視力でもわかるぐらい、結果を出力する画面が止まって見える。

なお、C 言語のような for 文は bash でも使えることがあとでわかったが、 いじってしまったあとでは遅い。今回は while 文のバージョンで掲げる。

bc

まず、bc について述べる。bc とは UNIX 系の OS で標準装備されている対話式プログラムであり、 また、この bc で使われる言語も指す。標準文法は C によく似ている。 私が使ったのは GNU 版で、これは標準 bc の文法から拡張されている。 たとえば、標準文法では変数は 1 文字のみであるが、GNU 版では変数は 1 文字に制限されない。 また、標準文法では if 文に else 節を書くことはできないが、GNU 版ではできる。

上記で述べた通り、bc の文法は C 言語の文法に近いので、ほとんど修正していない。 修正箇所は、文末のセミコロンの削除、print 文の修正などである。 また、j++ のように1行で ++ 演算子を用いている文は、 その行で式として評価されるために変数が表示されてしまうため、j += 1 のように変更した。 なお、bc の長所は精度が指定できることである。もちろん、時間や記憶容量などとの兼ね合いもあるが、 好きなだけの精度で計算できることは驚異である。

C 言語

ほとんど「技法」の通りである。 配列 j と s のサイズを和の最大数 n と同じく 1000 としたが、これはもったいない。 おそらく j と s のサイズは ceil(sqrt(n)) + 2 で収まるはずであるがその見極めが難しかった。下手をすると、 アドレスエラーになることが目に見えていたのだ。

C++

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 先生に言われた、 「こういう解法では無駄な部分を計算することになる。領域と時間がもったいない。」 というお叱りを思い出し、 やっとこの年になって「このように改善できましたよ」ということができるようになった。

Common Lisp

Scheme でのリスト内包表記に味をしめて、 Common Lisp でも使ってみることにした。 incf-cl を使うのがミソである。 Common Lispでリスト内包表記(kikkii.hatenablog.com) を参考にした。

Dart

JavaScript のプログラムとほとんど同じである。 変更点は、文末にすべてセミコロンを入れたこと、 JavaScript の配列を Dart のリストに変えたこと、これに伴い新規にリストの要素を作るときは、 add メソッドを使ったことだ。リストの参照は配列と同じようにインデックスで可能である。

Elixir


Scala 版のコードを基に Elixir 用に手直しした。 Scala 版と比較して、中間の変数を多く入れている。 これは、Haskell 版と同じ理由で、 Elixir の理解が足りないためである。 コーディングスタイルがめちゃくちゃで、カッコがあったりなかったり、 無名関数の書き方がいくつもあったりでとても見せられる物ではないが、 勘弁してもらいたい。

Erlang


Common Lisp 版や Scheme 版と同様、 リスト内包表記を使っている。「プログラミング Erlang」の pp.43-44 で、 ピタゴラス数をリスト内包表記で生成している例を見て、これならできそうだと思った。 Scheme や Lisp との違いは、Erlang ではリスト内包表記が言語に組み込まれていて、 新たなライブラリやパッケージをロードする必要がないことである。 今気づいたのだが、リスト内包表記を標準で装備している Haskell や Python で、 なぜリスト内包表記を使わなかったのだろう。

Forth


詳しくは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

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

F#

拡張子は fs, fsi, fsx, fsscript などが用いられている。 これもリスト内包表記を用いている(個人的には、for ... in ... do という構文はリスト内包表記のような気がしない)。

Go 言語

上記は go fmt を通したあとのコードである。 C からほとんど変えていない。配列の初期化宣言のところ以外は、 ほとんど C のコードと同じだろう。配列のかわりにスライスかマップを使えば Go らしさが少しは出るのだろうが、私の実力ではできない。

Haskell


Scala のコードを基に Haskell 用に手直しした。 Scala 版と比較して、中間の変数を多く入れている。これは、Haskell の理解が足りないためである。 Haskell と Scala は似て非なる言語だということを痛感した。 sortBy を実現するのに import Data.List が必要だということに気づくのにかなりの時間を費やした。 また、Scala の flatten に相当する関数は Haskell では concat になるということを調べるのにけっこうな時間がかかった。 おまけに、標準出力ができるのが main だけというのを理解するのにさらに時間がかかった。

Java

Java は仕事で使ったことが全くないので、"hello, world!" さえ表示できない。 そのため苦労した。C 言語にかなり手を加えた。 HashMap の取り扱いも苦労した。他の言語であれば、配列であるかのようにアクセスできるのに、 Java は put メソッドと get メソッドでアクセスしなければならない。依怙地だ。 なお、C++ や Perl と同じように、お役御免になった HashMap の要素は remove メソッドで消している。

JavaScript

AWK とほとんど変わらない。 AWK から変えたのは、変数宣言 (const|let) を追記したこと、 変数 j と s を配列として初期化したことぐらいだ。

Kotlin

この問題はリスト内包表記を使うことで楽に解けるので、Kotlin の解法でもこれを使っている。一番参考になったのは、 List Comprehensions (rosettacode.org) だ。他の言語の解法では、リスト内包表記の map する順序を考えることで和の昇順に並べられ、 その結果 sort が不要にすることができたのだが、この Kotlin の Map と flatMap、filter の入れ子では、 和の昇順に並べるプログラムができなかった。恥ずかしいが、Scheme と同じように、まずは該当する組をすべて求めたあと、 三つ組で和を表す最初の要素をキーにして昇順にソーティングしている。

Perl

AWK 版を元にしている。 Perl でプログラムを組むのは 10 年ぶりだ。変数に $ をつける、文末に C と同じように ; をつける、 ということすら忘れてしまっていた。 変数 j と s はハッシュで作っている。C++ と同様に、お役御免になったハッシュは delete で消している。

Python

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 言語

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) に変換する関数である。 これらの再帰を使った関数をもとに平方数の和を計算して、ソーティングしたり抽出したりしている。

Rust

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 のページに記した。

Scheme

ScalaHaskell など、ほかの関数型言語の真似をしようとして書こうとしたが、どうしてもうまくいかない。 何のために関数型言語を学んだのだろうと落胆して、Scheme や Scheme の実装である Gauche についてあれこれ調べていた。 すると、「リスト内包表記」というキーワードが目に留まり、それからいろいろ調べた。 その結果、リスト内包表記をサポートした srfi-42 を使えば、すっきりしたプログラムになることがわかった。 結局は、私がプログラムした部分はほとんどなく、 Gauche ユーザリファレンス:先行評価的内包表記 (practical-scheme.net)にあるタクシー数の生成例や、 マクロの効用 (practical-scheme.net) にある「リストの内包表記 (List comprehension)」で示されたピタゴラス数の生成例(pyth n) とほとんど変わらない。私がしたことは、上限の二乗和から平方根をとって個別の数の上限にしたことと、 二乗和に関してソーティングしたことだけである。なお、 三つ組ごとに改行を入れる処理はしなかった。

Tcl

Tcl とはアプリケーション組み込み用言語として設計された小さな言語。 Tcl の有用性を示すために付属としてつけた Tk が GUI 作成にうってつけであったため、 Tcl/Tk という形で世の中に広まった。今回は Tcl のみで書いている。

変数は、参照時には $ をつけて、代入の左辺値となるときには $ をつけない。これに注意して、 perl のソースコードを基にして改造した。if や while 、for の条件節などでは () ではなく、{} を使うことに注意する。 また、代入時に式を評価するときには都度 expr をつけなければならない。これで苦労した。

なお、WSL2 で tcl を起動するには、
$ tclsh
とする。(2023-04-27)

まりんきょ学問所コンピュータの部屋 > コンピュータ言語遍歴


MARUYAMA Satosi