見出し画像

Retro-gaming and so on

RE: Racketで何か・・020 とりあえずLoLのグラフは終わり

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

表示するだけなのに副作用とか・・?

いやいや、表示副作用なんです。
言い方を変えると、副作用の一つに表示がある

実はコンピュータでは、入出力は全部副作用です。極端な言い方すると、計算機なんで、計算以外は全部副作用と考えて構いません(※1)。ファイル読み込みやファイル書き込みも副作用です。
Schemeで言うとreaddisplayも副作用。

例えば、変数fooを次のように定義して、

> (define foo (display "hoge"))
hoge
>

確かにhogeと印字される。
じゃあ、foo(display "hoge")と言う結果が代入されてるか、ってぇと。

> foo
>

何も返ってこない。つまり、displayには返り値がない。返り値が無い、って事は関数としては主目的がない。よって、表示する、ってのはdisplayの関数の作用ではなくって副作用だ、って事です(※2)。displayって名前から考えるとヘンな話ですが、コンピュータサイエンス的にはそうなります。

なお、純粋関数型言語では理論上、入出力やファイル読み込み/書き込みは存在しません。
と言う事は純粋関数型言語のHaskellには、結果入出力やファイル読み込み/書き込みが存在しない、って事になる。
しかしそんなプログラミング言語は使い物にならない
当然そんなバカな事はありえないわけで、Haskellでは入出力やファイル読み込み/書き込みを関数として扱えるようにする為、悪名高いモナドと言われるものを導入しています。

こっちがmapですけど・・・出力結果一緒だよなぁ・・?

そう、出力結果は一緒。でもfor-eachには返り値がありません(繰り返すけど出力「自体」は返り値でも何でも無い)。返り値があっても構わない/気にしない場合は、結局mapを使ってもいいんです(Racketだと'(#<void> ... ) ってリストが返ってくるが、役に立たないリストである)。

pair-for-eachの挙動が分からなかった・・

うん、そういう場合はサンプルとして参照実装を見てみるのが一番です。
単純には定義は次のようなカンジですね。

;;; リリカル☆Lispで取り上げられてたmapのような、可変長引数じゃない簡易ヴァージョン

(define (pair-for-each proc ls)
 (unless (null? ls)        ;; 終了条件
  (proc ls)           ;; ls を引数に proc を実行
      (pair-for-each proc (cdr ls)))) ;; pair-for-each を (cdr ls) を引数に再帰呼び出し

thunkで包む・・って・・これもLoLだとすごくアッサリなんですよね。

まぁ、新しい用語が出てきて大変、ですね。
そのアッサリ目の内容、Land of Lispではこう書いてある。

Lisperは、今すぐに実行したくない計算を包んでおくのに、ゼロ引数の関数を良く使う。この目的で使われる無引数関数は通常サンク(thunk)やサスペンション(suspension)と呼ばれている。

今すぐに実行したくない計算・・・・・・はい、遅延評価ですね。
上の文章はLisperを強調してますが、実際は、ラムダ式を持つ言語で直接遅延評価を書き下したい場合は、やりたい事を無引数ラムダで包み、要するにこの無引数のラムダ式をthunkと読んでるのです。
例えば、ネットをちと検索してみると・・・・・・このテの話に食いつきがいいのはJavaScript通の人たちかな。浅いJavaScriptな人達じゃなくって通の人たち。
およそ10〜15年くらい前の記事だけど、Haskellの遅延評価が取りざたされる中でJavaScript通の人達が割に食いつきが良かった。
一つはPythonのラムダ式がショボいのと、Rubyじゃあまりにも当たり前の話なんで、「そろそろ初心者から脱却する」JavaScriptの人が「遅延評価って何ぞや?」って話題に興味をそそられてたのね。
例えば有名なたらい回し関数の話。JavaScriptで定義通りに書くと次のようになる。

function tarai(x, y, z) {
 if (x <= y) {
  return y;
 } else {
  return tarai(tarai(x-1, y, z), tarai(y-1, z, x), tarai(z-1, x, y));
 }
}

これは重い。tarai(12, 6, 0)なんかを計算させるとクッソ重くてシャレになんない。
ところが、thunk、つまり無引数のラムダ式を与えてこれを書き換えてみる(※3)。

function tarai(thunk_x, thunk_y, thunk_z) {
 x = thunk_x();
 y = thunk_y();
 if (x <= y) {
  return y;
 } else {
  return tarai(
   function(){return tarai(
    function(){return x - 1;}, thunk_y, thunk_z);},
   
function(){return tarai(
    function(){return y - 1;}, thunk_z, thunk_x);},
   function(){return tarai(
    function(){return thunk_z() - 1;}, thunk_x, thunk_y);});
 }
}

かなり長くなるけど(苦笑)、こう書き換えてthunkとして引数を与えてみる(下線部がJavaScriptの無引数ラムダ式、つまりthunkにあたる)。
例えばこんなカンジで。

tarai(function(){return 6;}, function(){return 12;}, function(){return 0;})

そうすると、フツーの先行評価だとクッソ時間がかかる計算が一瞬で終わるからビックリするでしょう。
いずれにせよ、無引数ラムダ式は遅延評価を持たないプログラミング言語で遅延評価させるためのテクニックであり、それを特にthunkと呼びます。
なお、Schemeには遅延評価が組み込まれてるけど、基礎的な2つ〜3つの関数及びマクロしか提供されていないのでSchemeの組み込み関数は遅延評価ベースとして設計されていません。
また、ANSI Common Lispには遅延評価がない。そういうわけで、この時点ではLOLの遅延評価絡みの説明は異様にアッサリせざるを得ないのです(苦笑)。
ANSI Common Lispにはマクロがあるんでフツーのプログラミング言語で遅延評価を言語ベースで組み込むのは諦めなければならんトコをマクロで実装可能なんだけど、そこに行くには18章まで進まないとならんのです。





う~ん、#:existsってオプションの意味が分からん・・・
文脈から推測するに、同名のファイルがもしも存在していたら・・って事かな?

その通りです。
デフォルトは'errorになってる。こういうカンジで、シンボルで指示する模様・・・・・・これはSchemeじゃなくってRacket拡張かな。
ファイルを連結しろ(append)とかアップデートしろ(update)とか置き換えろ(replace)とか・・・都合の良いように指定できるみたい。

ネットで検索してたら「サンプル無くて分からねぇよ」という外国人の書き込みが。激しく同意!

そうなのよね(苦笑)。
Racketは機能が多くて、ドキュメンテーションも一見豪華に見えるんだけど、褒められたモンじゃない。
っつーか、Scheme実装は全般的にドキュメンテーションがホントダメ。元々プログラミング言語研究の為に発達してきたようなトコがあって、研究者向けではあるけど実用的じゃない、ってのはこの質が悪いドキュメンテーションが証明してると思います。
んでもっと言っちゃうと、オープンソース系ってホント一般的にドキュメンテーションが劣悪なんだよなぁ。連中色々言うけど、実際問題、Windows絡みのMicrosoftを始めとするプロプライエタリソフト制作陣が作ってるドキュメンテーションの方が平均的には質が良いです。フリーソフト見てもそうだもん。
ポール・グレアムなんかもオープンソース系の人たちの方がモノを書くのは得意だ(※4)、とか言うけど眉唾です(※5)。明らかにプロプライエタリなソフトウェア作ってる人達の方が「分かりやすいモノを」書きます。
余談だけど、以前、Linux系のサウンドサーバの事調べた事があったんだけど、Linuxの人たちが書いた文書は参考にならんで全然ダメで、逆にWindowsに移植されたブツに付いて書かれてるヤツの方が文書的にはまとまってて読みやすくて役に立った。書いてた人はWindowsでプロプライエタリなソフト開発してた人だと思うんだけど、ホントLinux系はダメだな、って痛感した。
オープンソースでPythonが大変人気がある、ってのはドキュメンテーションがオープンソース陣営の割には良く書かれてるから、なのね。
RacketがPLT Schemeから改称した大きな理由は「Pythonみたいな可愛らしい(?)名前にすれば人気が出るんじゃないか?」って事だったんだけど、Racketの人たちは全然ズレてると思う(笑)。明らかにドキュメンテーションがダメなんだよな。そこに気づかない限りPython人気は超えないと思う。

何故かWindowsPCにGraphvizがインストール出来ないんです。作業までは出来るのにデスクトップにアイコンは出ないしプログラムリストを見てもuninstallしか無いしなぁ・・

あ、多分デスクトップにアイコンは出来ないでしょう。CLI(コマンドライン)なソフトなんで、DOS窓とかPowerShellじゃないと起動しないでしょう・・・・・・なんかPowerShellだとパス別に通す必要があるのかしらん。

※1: ちょっと大げさな言い方だけど殆ど一致してる。
じゃあ文字列操作等は・・・?と考えると、原則的にコンピュータ内部では数値なんで破壊的変更をしなかったら計算になる。
ただ、この辺は、結局メモリ効率の問題が絡んでくるんで、破壊的変更が必要な場合もあって、そういう場合は返り値が存在しないんで、副作用になったりする。
また、数学的プログラムでも、メモリ変更、つまり代入は数学的意味ではないところで計算ではない。よって破壊的作用目的なので、これも副作用になったりする。
Lisp用語でletを使った変数への一見数学的代入を代入と呼ばずに束縛と呼ぶのは、破壊的変更を行わないから、である。Lispのletはラムダ式の変形で、ラムダ式の引数に値を与えてるだけで、コンピュータ操作上の代入、つまりメモリ上の値の変更を伴わないので、代入と言う言葉を使うのを嫌ってるのだ。

※2: 何度も書くが、コンピュータサイエンス用語では結局、「返り値が無い関数」を手続き、あるいはプロシージャと呼び、手続き/プロシージャは副作用目的で存在する。

※3: 実はこの書き方はかなり古い書き方だが、Mozilla(Firefoxの提供元)が提供しているスタンドアロンのJavaScript(EcmaScript)処理系であるRhinoは最新のEcmaScript仕様に準じていない。
Rhino愛用者としては悔しいのだが、しょーがないので古い書き方に準じてる。
最新のEcmaScriptだともっと短い記述でラムダ式を構成する事が可能だ。

※4: 正直言うと、プログラマって「自画自賛」が多い気がしてる。
例えば「プログラマは理論的だ」と言うのは、プログラマじゃない他者からの「評価」があって初めて成り立つのだが、プログラマの外部からプログラマが理論的だ、って評価される話は正直聞いた事がない。
つまり、ここでプログラマ自身が言う「プログラマは理論的だ」と言うのは客観的評価でも何でもないのだが、こういう「自画自賛」が割にまかり通ってる気がするし、明らかに「数学者は理論的だ」と言う評価とは質が違うと思う。
正確に言うと「プログラマの中には理論的な人もいる」ってレベルでしかないのではないか。

※5: ポール・グレアムは偉大なLisperだが、同時に眉唾を書く率も多い気がしてる(笑)。
例えば、彼は「Microsoftのソフトは一回も使った事がない」と豪語してるが、Microsoftは信用がならない、と言ってる。
何故に「一回も使った事がない」会社のソフトを批判できるのだろうか。これを眉唾と呼ばずに何と呼べばいいのか、ちと分からん。
なお、ポール・グレアムのエッセイを読んで、そのあとバランスを取るとすればジョエル・スポルスキエッセイを読むことをオススメする。
彼はMicrosoftに勤めてた事があり、初期のVBAの設計者の一人であって、如何にMicrosoftが顧客を大事に開発してる企業であるか、あるいは「だったか」と言う話を書いてて、同時に単純なオープンソースモデルは信頼出来ない、と言う鋭い洞察を発信している。
ポール・グレアムとジョエル・スポルスキ、どっちが正しいのか、と言うと、総合的にはジョエル・スポルスキの方が正しい、と思ってる。
なお、彼のエッセイは書籍版も出てるので図書館で探してみたら良いだろう。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「RE: Racketで何か」カテゴリーもっと見る

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