見出し画像

Retro-gaming and so on

RE: プログラミング学習日記 2022/10/29〜

星田さんの記事に対するコメント。



 なるほど・・実は「写経(ただ打ち込むだけ)」って意味無いだろとは思ってました

うん、そりゃそうだ。
ただし、写経ってのはそもそも「ただ打ち込むだけ」じゃないんだよね。
「読む」事が重要なの。「読みながら打つ」から理解力が上がるわけで、「読まずに打ち込む」ならそりゃ効果がない。
この先生もその辺分かって言ってはいると思うんだけど、学生で、とにかく「打つ」事ばっかやってる奴らが多いんで、警鐘を鳴らしてんじゃねぇのかな。
実際、写経やってると、「本で印刷されたコード」を読んでも意味が分からなくても、「打つ」事で「あぁ、なるほど、そういう意味か」って分かる事がある。
言い換えると、「打つ」事で「読解力が上がる」のがむしろ写経効果なんだけど、意外とそこまで到達するヤツが少ない、って嘆いてるんじゃないかしらん。



 C言語で関数型プログラミングが出来るか?という話題の解答。なるほど・・え?じゃあ高階関数とかどうすんの?これは興味が湧いてまいりました。

結論から言うとCには高階関数なんぞない(笑)。Cはないないづくし、なんだよ(笑)。
例えばだな。CでLispで言うmapみたいなのを作ってみたい、とする。
オーソドックスには次のような関数になるんだけど。


ここでint (*proc)(int)と言う不可思議な記述が関数mapの最初の引数に現れてるんだけど。それがその文で書かれている関数ポインタだ。意味は、int型を引数に取り、int型を返す(一般的な)関数procを指すポインタ、って意味だ。
うげ、なんつー書き方だ、こんなの読めないだろ、とか思うでしょ?実際問題C言語慣れしてる層でもあんま使いたがらない。何故なら「あれ、どーだったっけ」と悩む程メチャクチャ難しい書き方をCが強要するから、なんだわ。
C言語で皆がポインタは難しくここで挫折する、とか言うけど、真実はポインタ自体が難しいワケではなく、単に、C言語のポインタ周りの文法がメチャクチャなだけ、なんだわ(笑)。ぶっちゃけ、C言語はアドホックに拡張されてきた経緯がある為、フツーの人間には読解不可能な程メチャクチャな文法になっている(笑)。 => 参考記事: 配列とポインタの完全制覇
次に、引数の二番目に数nがある。これはLisp系言語のmapにはない。これが何なのか、と言うと「配列の長さを教える為」だ。言い換えると、C言語には配列の長さを調べる仕組みがない。Lispで言うlengthとかPythonで言うlenがねぇんだ。
以前、別のトコにも書いたと思うんだけど、Cでの配列、ってのは実はフツーの高級言語が持ってる配列ではなく、単にパソコンのRAMそのまま、なんだ。そして具体的には「どこから使います」と言う情報(アドレス)しかやり取りしない。従って終端情報がない。終端情報が無い以上、どこで終われば良いのか分からない。そしてヘタなプログラムを書くと、「使う予定だったメモリのエリアを飛び越えて」、有名なセグフォを喰らう、って寸法なんだ(※1)。
従って、Cで配列をやり取りする際には、「想定された配列の長さ」をわざわざ指定しないとならない。クッソメンドイ(苦笑)。
余談だけど、Cで端末からコマンドライン引数を使う場合、main関数は

int main(int argc, char** argv) {...

と書き始めるわけだけど、argvはさておき、argcって何よ?ってのはこれが理由だ。argvは文字列(※2)の配列なんだけど、C言語はその受け取った文字列の配列の長さを知る事が出来ないから、配列の長さを「argcとして教えなきゃならない」んだ(笑)。結果、端末がその「長さ」を自動でC言語で書かれたプログラムに「教える」事になる。
これが同じC言語系の言語、Javaだと

public static void main(String[] args) { ... 

と引数の中身が随分とスッキリとしてる(とは言っても外側はメチャクチャ記述量が多いが・笑)。
これはJavaの場合、文字列の配列の「長さ」を知る事が可能になってるから、だ。確かにJavaはC言語の欠点を改良するようにデザインされている。
次、関数本体で、

int* p = (int*)malloc(sizeof(int) * n);

って書いている。これは「int型の容量のメモリをn個分だけ用意し、その先頭アドレスをpに代入しろ」って意味だ。
なんでこんなモン書かなきゃなんないのか、と言うと2つばかり理由がある。

  1. そもそもC言語では配列をやり取り出来ない。
  2. LispやPython、Rubyのようなモダンな言語だと自動でリストを生成してくれるが、Cにはそんな洒落た機能がない。
1.は先にも書いた通り、そもそもC言語には高級言語で言うトコの配列が存在しない。剥き出しのRAMがその正体だ。従って関数間で「受け渡し」出来ないし、関数の返り値としても返せない
実際、このmapの定義だと一見、配列であるlistが受け渡されてるように見えるが、実はそうじゃなくって、ある配列の先頭の情報(アドレス)しかやってこない(※3)。
一方、メモリを確保して配列を生成した場合(これを動的配列と呼ぶ)、それは受け渡し出来るように設計されている・・・事実上、これもあくまで「メモリを確保した」一番先頭のアドレス、って事なんだけどね。
2番はそのまんま、だ。Lisp/Python/Rubyは破壊的変更を避ける為に新しくリストを自動生成してくれたりする。一方、破壊的変更WelcomeのC言語の場合、原理的にはメモリをコピーしつつ新しく配列を生成する機能がない(と言うより、ライブラリを使わないと実現出来ない)。
結果、人力でその機能を「書かなきゃ」ならないわけだ。この辺もC言語脳と、例えばLisp使いの「発想」が違う部分だ。C言語脳だと直球的に与えられた配列の値を破壊的変更する事を選ぶだろう。しかしながら、それをやっちゃうとLisp/Python/Rubyなんかの「モダンな言語」のmapにはスペックで到底及ばない。
繰り返し部分はLispなんかでは再帰を行うトコなんだけど、C言語の配列はLispのリストなんかより遥かに脆弱だ。人に拠っては「ポインタ演算で配列の先頭アドレスをズラせばエエんちゃうの?」って思うだろうけど、ポインタ演算自体がメンド臭い。しかも、別に末尾再帰したトコで仕様上は最適化されるわけでもない(※4)。従って、フツーにfor文で回した方がイイだろう。
ここでも分かるだろう。剥き出しのメモリである「配列とは呼べない」配列を抱え、特に再帰で計算を行うメリットがまるで無いC言語だと、LispのmapやPythonのリスト内包表記に拒否感を覚えるC言語脳しか基本育たない、と言う事が。
あとは、生成した動的配列の先頭アドレスを抱えたポインタpを返すだけ、だ。

とまぁ、辛うじてC言語でもmap「らしき」モノは書く事が出来る。
ところがだぜ。
このmapの第一引数の関数ポインタを見てみる。int (*proc)(int)だ。意味は「int型を引数に取り、int型を返す(一般的な)関数procを指すポインタ」だった。ここで分かるのは関数procint型の引数を「一つ」受け取る、って事だ。「一つ」だぜ?二個は取れない、って事だ。
何を当たり前の、って思うかもしれないけど、そもそもLispのmapは受け取るリストの個数に合わせて、その数の引数を取れるクロージャをぶち込めて仕事が出来る筈だ。
ところがこの汎用性をC言語では得られない。仮に二個配列を引数に受け取るとしたらint(*proc)(int, int)と言うを使ったmapをまた別に作らないといけない。
受け取る配列が三個なら・・・と言う事が容易に想像出来る。
それだけじゃない、今はprocと言う関数はint型を返す前提だったんだけど、doubleを返す場合は?引数がintじゃなくってchar*の場合は?全部のパターンを列挙するのは現実的じゃない。つまり、C言語で汎用的なmapを書くのがそもそも無理だ、ってこった。あまりにも制限が多すぎて、とてもじゃないけどユーティリティになりゃしねぇ。
分かる?C言語は自由で何でも書ける、とか言うヤツがいるけど、実際は殆ど何も出来ない、って言った方がイイんだ。どう考えても非力過ぎて(関数型言語を使い慣れてる我々にとっては)とてもマトモなプログラミングが出来るとは思えない。
そこのページで書いている

現実的には煩雑すぎて使い物になりません。

ってのはそういう事だ。

全ての言語はチューリング等価であるという事実は、厳密に言えば、 いかなるプログラムもどんな言語を使っても書けるということだ。 じゃあどうやって? このようなケースでは、 力の弱い言語でLispインタプリタを書くことで実現できる。 

もそういう意味だ。実際多くのLisp系実装はC言語で書かれてる。そして「C言語で何かを直接、簡単にコーディング出来ない」以上、C言語の持つ解決策は「C言語でまずはLispを作らざるを得ない」ってこった。C言語を捨てながらC言語でLispを作らなスペック的には負けてしまうんだ。
何故にC言語脳がヘタなPython教育を行うのか、意味が分かってきたと思う。彼らはここまで「機能がない」言語を使って「低レベルな動作でしか物事を考えられなくなった為」おかしな解決策でおかしな言語の使い方を紹介せざるを得ない、ってワケだ。
「C言語は基礎」と言う妄言のとんでもねぇ意味がどんどん分かってくる、と思う。C言語を学習しながら、ね(※5)。

 今更ですけど「プログラミングは独学」との事。えっ・・?多大な労力と時間をかけて学ばれたことを惜しげもなく教えてくださってるんですね・・ありがたい事ですm(_ _)m

う〜ん、でも結果さ。もっと「プログラミング教育」って簡単に行える筈なんだよ(笑)。僕が10年かかったとしたら、1年で同じ事やるのも不可能じゃねぇだろ、って気がホントはしてる。
僕が「多大な労力と時間をかけて」だとしたら、そもそもプログラミング教育のカリキュラム的なアイディアがそもそも間違ってるだけなんじゃねぇの?とか、正直思ってるくらいなんだ(笑)。だってマトモな教科書が殆どねぇ、って事がそれを証明してんだろ(笑)?
だからショートカットで学べるんだったらガンガンショートカットしてってイイと思う。「料理人になるなら皿洗いから初めて・・・」ってのが無駄だとすれば、実際のプログラミング教育っつーのも「それくらい無駄をやってる」ってだけの話だと思ってんだよな(笑)。なんせプログラミングもそもそも理論とは全く関係がない職人の世界の話だから(笑・※6)。
言い換えると、これも何度か指摘してっけど、数学とかと違って、プログラミング教育法のノウハウがまだ無い、ってだけの話なんだ。

要望か〜・・CLIで画像が使えるってのが楽しいので音声ファイルも使えたら更に楽しそうですよねぇ。

一応、MIDIファイル関係の機能があるんだけど、これはまだ試した事ないです。
ひょっとしたらPC備え付けの音源を「自動演奏」出来る可能性はあるんだけどねぇ・・・そのうち、ゲーム関係の記事書いて、その辺説明出来るかもしんない。

※1: カッコいい言い方をすると境界線問題と呼ぶ。

※2: 厳密にはC言語には文字列さえ存在しない。あるのは「文字の配列」だ。
よって、文字列さえ「終端が分からない」筈なんだけど、幸いな事に、文字の配列の末尾にはヌル文字、と呼ばれる"\0"が含まれる、と言う事が仕様によって決まってる。
従って、C言語の文字列だけ、はフツーの配列と違ってもうちょっと扱いが気楽になる。
と同時に、文字の配列には末尾に'\0'を付け加える、と言う事は配列としては含まれる文字数+1の長さが必ず必要、と言う事で、これを忘れてると、またもやセグフォこんにちわ、と言う悪夢に見舞われる事となる。

※3: 実際は、Lispのリスト自体がポインタの連鎖で構成されていて、一見「リストそのもの」を受け渡しして返せるような見かけになってるが、内部的にはやはり先頭アドレス自体を受け渡ししてる。Lispのcdrは、言っちゃえば、ポインタの連鎖を先頭から手繰っていく為の機能だ。
ただし、LispのリストはC言語の配列とは違って、終端情報を持っていて、通常、フツーのリストはケツで必ずNILと呼ばれるアドレスを指すようになってる為、結果、処理系がリストの「長さ」を把握出来るようになっている。

※4: GCCClangと言った実装は、最適化レベルを上げると末尾再帰最適化を行う。ただし、これは仕様の要求じゃなくって、実装上の拡張だ。

※5: 他にもC言語にはローカル関数がない。Lisp系言語に於ける

(define (foo x)
 (define (bar y)
  (...

なんて書き方が出来ない。
一方、Pascalだとローカル関数が扱える。機能的に見てもCはPascalに負けていて、結果、Cは高級言語、と言うよりは「毛が一本生えた程度のアセンブリ言語」だ、ってのがその実態だ。
この、ローカル関数や、翻るとクロージャ、そして無名関数って概念そのものがC言語ユーザーには「難解」に映るらしく、結果、「C言語は基礎」どころが、重要な概念を「学べない」言語だと言う事が出来る。
実際、個人的にも、ローカル関数やクロージャ、そして無名関数の概念を全く理解出来ない、と言うC言語erに何人も遭遇している。

※6: そもそも「計算機科学」と言うが、科学でも何でもない。第一、自然科学ではないんだ。
「計算機科学」と言うのは、言っちゃえば「他人が作ったモノを使って他人が敷いたルールに従えるのか否か」ってだけの話であって、自然科学とは様相が全く違う。
要は「他人の褌を使って相撲を取る」能力が問われている。
「どこまで行っても他人の掌の上」ってのが原則で、結局悩ませられるのは、「自然に対して悩む」んじゃなくって「他人が作ったルールとデザインに対して悩んでる」だけだったりする。
当然そんなモンに対しては拒否感が出てもしょーがないわけで、これを理論と呼ぶのなら、川上宗薫宇能鴻一郎のどっちの文体が好みで「正しいのか」と言うバカバカしい議論と本質的には変わらないレベルの事を「理論」と呼んでるんだ。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「RE: プログラミング学習日記」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事