JavaScriptには関数の末尾再帰最適化がないので関数型には不向きだというデマ

冒頭報告

 

当方を不当に侮辱、誹謗中傷した人間から謝罪文が掲載されている。

 

nantonaku-shiawase.hatenablog.com

 

本論

 

anond.hatelabo.jp

関数型プログラミングが『銀の弾丸』であるという非常識な常識2022のなにがダメなのかわからない人が多いようなので、個人攻撃をまったくせずにダメ出しする。

まず言っておくが、私はあの記事ほとんど読んでいない。しかし、簡単ダメ出しできる。

 

また読まずにダメ出しするというアレが出てきた。

しかも、また致命的なレベルで勉強不足の分際で「しかし、簡単にダメ出しできる」とか宣っている。

こういう変な界隈の連中は非常に思い込みが激しいのと同時に、まともに勉強なんてしていないので、とんでもない勘違いワールド世界の住人である自覚もないのだが、ひとりで趣味でやっている分には「勝手にしろ」とか思うが、いざそれをこういう形で喧伝したり、ましてや他人様の著作にミソをつけるのが動機でやられたなら、こっちは黙っては居ない。

これまでこのパターンのアレな連中にさんざんこの類型の不条理なネガキャンをやられてきたし、問題は、それが間違ってると知ってる連中は黙ったままだし、問題のネガキャン当事者以下のアレはそろって、同意する、とかいって、あろうことかそのデマに「信憑性」のようなものを与えてしまう。非常に悪質である。

 

あの記事はめちゃくちゃ長いのに末尾再帰に触れていない

あの記事急所は末尾再帰である

記事内を「末尾再帰」で検索してみよう。1か所もヒットしない。「末尾」でも1か所もヒットしない。そう、あの記事はめちゃくちゃ長いのに末尾再帰に触れていないのである。では「再帰」ならどうだろう。11か所ヒットした。しかし、具体的な再帰コードはまったくない。長い記事内にあれだけ多数のコードを書いているにも関わらずである

 

再帰については、まさに冒頭のreduceそのものに帰着して説明は完了しているので、特に必要ないかな、とは思ったが、ああやっぱ馬鹿な中級者はこういう角度から勘違いsて攻撃してくるんだ、とわかったので、このエントリ終わったら加筆に取り掛かってやろうとは思う。

 

末尾再帰重要

「末尾再帰って何?」とか「再帰ってそんな重要なの?」と思う読者も多いだろうから、末尾再帰重要さだけ説明しよう。

あの記事は、forやwhileを使わないプログラミング手法を前提に書かれている。記事内を「制御」とかで検索すればわかる。

末尾再帰はforやwhileの代わりになるもので、そういったプログラミング手法には欠かせない。forもwhileも末尾再帰も使わないとなると、ツリー探索などのアルゴリズムを書くことが困難になる。(こういったことが苦手な私に思いつく他の方法は、setIntervalを無理やりforループの代わりにするくらい)

 

「あの記事は 前提に書かれている。」

とか読んでもないやつがねー(苦笑

for や whileは、だから記事に書いているとおり、Fold/Reduceになるんだよ(ばーか)というか読めよ?
と言ったところだろうか。

というかなんでこいつら「読んでない」ということを誇らしげに言いながらこういうトンチンカンなこと言って生きてて平気なのだろうか?
異常者だと思う。

 

JavaScriptはたいてい末尾再帰サポートしていない

そもそもほとんどのJavaScript実行環境は、末尾再帰サポートしていない。つまりJavaScriptはforやwhileを使わずに込み入ったプログラムをまともに書けるような言語ではない。あの記事に書いてあるようなことをする言語ではないのである。私は別にそれでもいいのでTypeScript使いまくってるけど。classとか好きだし。

あの記事JavaScriptを使っている理由は、JavaScriptが人気だからだろうか?もしそうだとしてもダメである。あの記事は「JavaScriptは、ほどんどの実行環境が末尾再帰サポートしていない、このプログラミング手法に適していない言語である」といったこ自体に触れていない。人気のある言語を使いたいなら、他の末尾再帰サポートしている人気言語を使えばいい。

末尾再帰をサポートしていない、そのとおりだ。

そんなもんなくても関数型コード書けるし、末尾再帰が必要なコードっていうのは、理論的にはラムダ計算からの再帰ループを実装することでチューリング完全性を示したり、数学の漸化式の数列をそのまま実装することで理論的にはみるところはあるが、リアルなコードでそれをそのまま書くのは「アホ」だ。

これは、リアルなコードでYコンビネータをもってFold/Reduce式を書く人間がいないのと同じ意味で「アホ」なのだが、

Factorial(階乗計算)やらFibonacci数列のコードでは再帰の概念の説明に役立つのでサンプロコードとして示されていることは多くて、特にHaskellに足突っ込んでる系の中途半端に関数型プログラミングを知ってる「つもり」の中級者がひたすらそのアホなコードを再生産していて、以上の引用文のようなアホなデマを流していて甚だ有害である。

 

あの記事は「JavaScriptは、ほどんどの実行環境が末尾再帰サポートしていない、このプログラミング手法に適していない言語である」といったこ自体に触れていない。

触れてないことなんて山ほどあるし、解説の流れの優先度ってもんがあるからね。

JSが末尾再帰をサポートしていない、なんてまったく重要な事柄ではないし、このプログラミング手法=関数型に適していない言語である、っていうのは、こういう勉強不足で知識不足の馬鹿連中の間違った知識にもとづくデマでしかない。

しかし、こういうデマを流して、しかも賛同する同類がいるとわかった以上、なるだけ早く触れざるをえないだろうということはわかった。非常に厄介である。

f:id:stq2050:20211220061934p:plain

f:id:stq2050:20211220061857p:plain

f:id:stq2050:20211220062033p:plain

 

 

以下、すでにこいつの返信は一部スパム認定されて見られないのでこちらの返信だけ引用する。しかしそれで十分である。

-----

 

 

まあたしかにそうだな。Haskell界隈の関数型の解説はかなり筋が悪い。永遠に、理路整然としてものが続くのは当たり前なんで、自分で理路整然としてものを書いたら、こういう馬鹿が読みもしないでネガキャンの挑戦してくるわけだ。

 

ちなみに見せた記事はこれ

en.wikipedia.org

In functional programmingfold (also termed reduceaccumulateaggregatecompress, or inject) refers to a family of higher-order functions that analyze a recursive data structure and through use of a given combining operation, recombine the results of recursively processing its constituent parts, building up a return value. Typically, a fold is presented with a combining function, a top node of a data structure, and possibly some default values to be used under certain conditions. The fold then proceeds to combine elements of the data structure's hierarchy, using the function in a systematic way.

 

関数型プログラミングでは、fold(reduce, accumulate, aggregate, compress, injectとも呼ばれる)とは、再帰的データ構造を解析し、所定の結合操作によって、その構成要素を再帰的に処理した結果を再結合して戻り値を構築する高階関数群のことである。
通常、foldには、結合関数、データ構造のトップノード、および場合によっては特定の条件下で使用されるいくつかのデフォルト値が提示される。そして、foldは、その関数を体系的に用いて、データ構造の階層の要素を結合していく。

 

Fold/reduce「再帰的データ構造」を結合操作する高階関数、これは本書でもじっくりと書いたこと。

 

On lists[edit]

The folding of the list [1,2,3,4,5] with the addition operator would result in 15, the sum of the elements of the list [1,2,3,4,5]. To a rough approximation, one can think of this fold as replacing the commas in the list with the + operation, giving 1 + 2 + 3 + 4 + 5.

In the example above, + is an associative operation, so the final result will be the same regardless of parenthesization, although the specific way in which it is calculated will be different. In the general case of non-associative binary functions, the order in which the elements are combined may influence the final result's value. On lists, there are two obvious ways to carry this out: either by combining the first element with the result of recursively combining the rest (called a right fold), or by combining the result of recursively combining all elements but the last one, with the last element (called a left fold). This corresponds to a binary operator being either right-associative or left-associative, in Haskell's or Prolog's terminology. With a right fold, the sum would be parenthesized as 1 + (2 + (3 + (4 + 5))), whereas with a left fold it would be parenthesized as (((1 + 2) + 3) + 4) + 5.

In practice, it is convenient and natural to have an initial value which in the case of a right fold is used when one reaches the end of the list, and in the case of a left fold is what is initially combined with the first element of the list. In the example above, the value 0 (the additive identity) would be chosen as an initial value, giving 1 + (2 + (3 + (4 + (5 + 0)))) for the right fold, and ((((0 + 1) + 2) + 3) + 4) + 5 for the left fold. For multiplication, an initial choice of 0 wouldn't work: 0 * 1 * 2 * 3 * 4 * 5 = 0. The identity element for multiplication is 1. This would give us the outcome 1 * 1 * 2 * 3 * 4 * 5 = 120 = 5!.

 

まあこの辺のことは、Wikipediaのタイトル記事(別に数学的にWikipediaがおすすめ記事と言ってるわけではない、Wikipedia記事であってさえも、というニュアンスがある)でも、普通に記載されている。

 

ja.wikipedia.org

には、

再帰呼出しの例[編集]

ここでは、階乗計算を再帰呼び出しにより実装する例を紹介する(自身の再帰呼び出しが、その計算における最後のステップになっているような末尾再帰再帰呼び出しの特別なケースであることに注意)。

C言語での例:

/* 階乗 n! を計算する */
int fact(int n) {
  if (n == 0) return 1; /* 脱出条件。0! は 1 である */
  else return fact(n - 1) * n; /* n! は (n-1)! に n を乗じたもの。再帰呼出し */
}

という、コードが掲載されている。

実はこれは無名関数=ラムダ式であってもこのような再帰関数を定義することはできて、それがラムダ計算だけでも「繰り返し処理」ができるという重要な証明となっていて、チューリング完全だ、とか理論上は重要だ。

それは大変結構なことなのだが、理論上の重要性だけにとどめおかないで、これが階乗計算の関数型スタイルの作法だ、みたいに伝言ゲームをやらかしているアホな教科書や教師、そしてそれを鵜呑みにして疑わない、この記事で紹介した中級者みたいな2人あるいは3人のような人間がいる。

リアルのコードで階乗計算にトップダウン方向の再帰関数を書くようなプログラマーは「アホ」である。

それこそ末尾再帰のメモリ効率化の問題やら、なんども非効率に同じ計算が指数関数的増加するのでメモ化が必要になって、デメリットしかない。

課題そのものがディレクトリ構造のように再帰構造ならばまだ許容できるが、

本書で掲載したコードを引用すると、

 

const multiply = (a, b) => a * b;const product = natural(4).reduce(multiply);// 1 * 2 * 3 * 4console.log(product); // 24

とすることも自由に可能です。

 

と、普段普通に階乗の手計算でやるのと同じボトムアップでFoldするreduceを使えば良いことだ。そしてこれは本書の冒頭からずっと継承して説明しつづけているコードでもある。

本人の該当ツイートが消えているのが残念だが、それが上記のツイートであり、特に彼は「他にどういうやり方があるんですか?」みたいに聞いてきたので、その返答が、これである。

 

続きをかくと、

 

 

 

とりあえず一旦UPする。

 

どうせ本書に追記しようとはおもうので、

ImuutableJSを使ったコードはどっかに残ってるはずだし、コピペできるかもしれない。