まず、
『関数型プログラミングに目覚めた!』のレビュー(Day-1)というのは、
@nonstarter
http://qiita.com/nonstarter
という素人かつ大学関係者を自称しながら、
言論人として文責をまったく負わずに済む、
素性のしれない匿名のQiita捨てアカウントによる、
書捨て記事であり、
「批判のための批判記事」です。
「レビュー」などと称していますが、対象となる書籍の良い部分は一切書いておらず、とにかく徹頭徹尾、揚げ足取り、歪曲捏造しているので、「批判のための批判記事」であることが露骨にあらわれてしまっていますが、本人はそのことに露ほども自覚がないのでしょう。
この【レビュー】読んでる人が、私のところへ送ってくださる感想は、
相手の方、(自称ではありますが)どっかの大学の教育者らしいのですよね。
そのような立場の人間が急いで私のコメントに返した内容は悲惨なものです。
終始、私がプログラミングの初歩も弁えない愚物であるかのように見せかけるための理屈をこねくり回しておよそ立場ある教育者としては恥ずべき、うっすら侮蔑をにじませた記述に終始しています。
ネットスラング的には「効いてる、効いてるw」様子で、泡くって焦ってる姿が見えます。確認が取れましたら、おちょくる感じで反論コメントを書いてみようかとも思うのですが、
しかし、コメントを読んで口をついて出た言葉は
「度し難ぇ・・・」
やっぱりゾンビじゃないか!面倒くせぇーーー(笑)(失礼しました)
ちょっと心配なのが、こうした事を続けるとコメントを削除されないかという点です。
せっかくのリンクが無くなってしまいますからね・・・
といった感じです。
これから、記事をわけて、住井 英二郎@esumiiさんとか、らくだの卯之助@camloebaさんに聞きたいのだけど、そもそも貴方たちはOCamlでデスクトップアプリ書けるんですか?への「回答」の精査と、この
『関数型プログラミングに目覚めた!』のレビュー(Day-1)
シリーズによる本書への批判のための批判を精査していきます。
【学習者要注意!】『関数型プログラミングに目覚めた!』のレビュー(Day-1)の「状態遷移関数」を使った非FRPのGUIコードがメモリリークする実用に耐えない不良品でしかなかったことについて 読者騙せて楽しいですか?
さて、私が、
住井 英二郎@esumiiさんとか、らくだの卯之助@camloebaさんに聞きたいのだけど、そもそも貴方たちはOCamlでデスクトップアプリ書けるんですか?
と示した「挑戦」、というか、
「あんたらどうせ最終的には実用に耐える関数型プログラミングのコードなんて提供できないんでしょ?(笑)」
という「確認」記事を出したところ、
案の定、メモリリークする実用に耐えない不良品しか提示されませんでした。
こういう連中の言うことを鵜呑みにしがちな【学習者要注意!】です。
問題のコメントとコード
Lambada 0 contribution May 30, 2015 11:03
本書の数々の重大な誤りに対し正当な技術的批判を加えても
著者から激しい誹謗中傷行為を受けるようですので匿名で失礼します。
OCamlで変数への破壊的代入を行わないでごく簡単に書くと以下のようになります。
(通常のOCaml環境ではwait_next_eventを用いるべきですが、
http://try.ocamlpro.com で動くようにloop_at_exitを用いています。
後者ではChromeもしくはFirefox推奨です。)
clickcounter.ml
(* クリックカウンター *)
open Graphics
let () = open_graph " 900x600"
let rec k c =
clear_graph ();
moveto 450 300;
draw_string (string_of_int c);
loop_at_exit [Button_down] (fun event -> k (c + 1))
let () = k 0 ;;
mousedrag.ml
(* マウスドラッグ *)
open Graphics
let () = open_graph " 900x600"
let rec k = function
| `Up ->
loop_at_exit [Button_down] (fun event ->
k (`Down(event.mouse_x, event.mouse_y)))
| `Down(x, y) ->
loop_at_exit [Button_up] (fun event ->
moveto x y;
lineto event.mouse_x event.mouse_y;
k `Up)
let () = k `Up ;;
いずれも岡部氏の主張とは正反対に、ストリームなどまったく用いず
状態渡しで実現されています。
逆に、岡部氏のコードはよく見ると state = x; という部分で
カウンタ変数への破壊的代入を行っており、何ら関数的ではありません。
(無駄に複雑なスパゲッティ・プログラムですが、よく読むと要するに
state = state + 1; と同じ破壊的代入です。)
ご参考になれば幸いです。
↑ 超おもしろいですね。既定路線すぎて。こんなことして技術者として恥ずかしくないんでしょうか?(笑)
いかにも「関数型プログラミングで実用的なGUIアプリ書いたことない、普段から書いておらず無頓着な低スキルコーダー」丸出しのコードが提示されました。
OCamlに詳しくない読者の方々にかいつまんで説明すると、これは要するに、再帰関数の無限ループを使ったコードです。
拙書にも書きましたが、再帰関数とは、自分自身を呼び出す関数のことで、無限ループになります。
それでですね、こういう再帰関数の無限ループをもって、GUIアプリを書いてはいけません。絶対に。
なんでか?メモリリークするからですよ。
関数を呼び出すと、その関数がが何やったかきちんとメモリに記憶しています。
コールスタックと言って、ちょっと気の利いたプログラマなら誰でも知っている基礎知識です。
コールスタック (Call Stack)は、プログラムの実行中にサブルーチンに関する情報を格納するスタックである。実行中のサブルーチンとは、呼び出されたが処理を完了していないサブルーチンを意味する。実行スタック (Execution Stack)、制御スタック (Control Stack)、関数スタック (Function Stack)などとも呼ばれる。また、単に「スタック」と言ったときにコールスタックを指していることが多い。コールスタックを正しく保つことは多くのソフトウェアが正常動作するのに重要であるが、その詳細は高水準言語からは透過的である。
再帰関数も当然、例外なくスタックを保持してメモリを消費します。
つまり、再帰関数を1回すごとに、ちょっとずつメモリを消費します。
しかもGUIのイベント拾うという、ただ単なる繰り返しをしたいためだけに、無意味にメモリを消費していく、つまり、メモリリークするんですね。
ひじょーにアホらしい設計であるのは、すぐ解ると思いますし、実際に、マウスポインタの動きを拾うイベントを無限再帰ループで拾ったりすると、スタックがオーバーフローします。いわゆるStackOverflow(スタックオーバーフロー)ですね。今どき耳慣れた言葉でしょうし、それほど超基本的でありがちな設計ミスというか、結構スキルレベル低めのバグです。
不良品コードの証拠として超頻繁なイベントをシミュレートしてスタックオーバーフローさせてやりました・・・
http://try.ocamlpro.com で動くようにloop_at_exitを用いています。
後者ではChromeもしくはFirefox推奨です
と、なんか偉そうに書いておられるので、実際に、その推奨されているChromeブラウザで
http://try.ocamlpro.com
にアクセスして、
動作確認のために、超頻繁なイベントをシミュレート、つまり、マウスイベントを待たずに、即時、再帰関数を呼び出すようにコードを改変して、
open Graphics
let () = open_graph " 900x600"
let rec k c =
clear_graph ();
moveto 450 300;
draw_string (string_of_int c);
k (c + 1)
let () = k 0 ;;
をペーストして、走らせてみると、即効でChromeタグの反応がなくなり、こうなりました。
「He’s dead, Jim!」
ChromeがRan out of memoryしたそうです。
別にコード改変せずとも、じっくり待てば、通常数万ループ程度で、どんなコードでもスタックオーバーフローするし、この提示された不良コードも最終的に必ずこうなります。
念のためですが、イベント待たずに即時呼び出ししたのは、メモリリーク、スタックオーバーフローの確認テストの「時間節約」のためです。
こんな超基本的な過ちを犯している、お粗末なコードをありがたがるQiitaの記事主
nonstarter 135 contribution May 30, 2015 11:18
Lambadaさん
著者の主張に対する反例となるOCamlコードをお示しいただきどうもありがとうございます。私の手元でも問題なく動作しました。デスクトップ云々の件もそうですが、ブログを含む著者の一連の主張は明らかに誤ったものであることが多く、いったい何がしたいのかを忖度することがおよそ困難であるように思います。やはり、いちいち誤りに反応せず、放っておくのが正しいのでしょうか。初心者の方が早期に問題に気がつけばいいのですが。
はい、笑止千万。なんでしょうか、この茶番は。
確かに、メモリリークする不良品のコードであろうが、なんであろうが、たかだかクリック数十回程度では、それは「手元でも問題なく動作しました!」となるように見えるでしょうね。
いうまでもなくスタックオーバーフローするまで、っていう時限爆弾抱えてますが。
そういう、使い込まないと判明しないスタックオーバーフロー、メモリリークのバグが一番たちが悪くて、あなたたち、そういうのを流布しているわけです。
やはり、いちいち誤りに反応せず、放っておくのが正しいのでしょうか。初心者の方が早期に問題に気がつけばいいのですが。
とか、偉そうに書いていますが、どの口で言ってるんでしょうか?
この人たちはそんなレベルにはいませんし、というか結構多くの技術者が、「ああこれはまずいコードだな」って気がついていると思うんですよ。気が付かずに、こういう連中の戯言を鵜呑みにして「ああやっぱり著者=岡部のいうことは間違いだったんだ」とか結果的に多くの初心者を騙している自覚を持ってください。かなり有害。
念の為に、JavaScriptで、イベントつきの無限再帰ループまわすコードは以下のようになります。
原理は一緒で面倒なのでコンソールでも動くように、ユーザ入力なしのsetTimeout
イベントを使って1秒毎のタイマーにしてます。
var f = function(c) {
console.log(c);
setTimeout(function() {f(c+1);}, 1000);
};
f(0);
これでも、確かに「著者の主張に対する反例となるOCamlコードをお示しいただきどうもありがとうございます。私の手元でも問題なく動作しました。」と、錯覚することは可能です。
きちんと1秒ごとに数字がカウントアップされていくのがJavaScriptのコンソールで確認できるでしょう。
しかし、同様に、超頻繁なイベントをシミュレートするために、setTimeout
を外して、即時、再帰関数呼び出しするようにコードを変えてみます。
var f = function(c) {
console.log(c);
f(c+1);
};
f(0);
結果、
17930
17931
17932
17933
17934
17935
17936
17937
17938
1793917734
:0RangeError: Maximum call stack size exceeded
はい、予想通り、というかこんなの既定路線なんですが、スタックオーバーフローになりました。
再帰関数で、イベント拾うようなコードを書いてはいけません。常識レベルですが、その程度の常識もないスキルの技術者の分際で、常識的な発言をしている論者に対して、偉そうに不当に糾弾しているのか、もしくは、
ちょっとマズイかな?でも、まあ岡部の言うことが間違っていると読者に思わせて騙せればそれでいいや、みたいなことでしょうか?
可能性は2つしかないですが、どっちにしてもかなりろくでもない連中です。
逆に、岡部氏のコードはよく見ると state = x; という部分で
カウンタ変数への破壊的代入を行っており、何ら関数的ではありません。
(無駄に複雑なスパゲッティ・プログラムですが、よく読むと要するに
state = state + 1; と同じ破壊的代入です。)
ご参考になれば幸いです。
あのねえ。なにが、ご参考になれば幸いだと。
自分のコーディング能力で理解しにくいコードを、
「無駄に複雑なスパゲッティ・プログラム」
とか、てきとーなデタラメ言うなと。
ヤ●ザの因縁つけ、当たり屋となんら変わらないやり方ですね。
カウンタ変数への破壊的代入を行っており、何ら関数的ではありません。
読むと要するに
state = state + 1; と同じ破壊的代入です。
違います。
拙書にも何度も書いていますが、関数ライブラリ自体は「ハードウェアモード」の命令型であったり、破壊的代入もします。
そうやって用意した、関数ライブラリを利用するコードが関数型であるということです。
こんな因縁が通るのであれば、FacebookのReactだって、FRPのコードは破壊的代入してますから、
なんら関数的ではありません。
と因縁つけられるわけですね。
まあ、Facebookの技術者は優秀なので、間違ってもこんなアホな無限再帰ループをもって、Reactライブラリ実装していないことだけは100%確かです。実際メモリリークなんてしていないから。
追記(2015/05/30):デスクトップアプリケーション??
著者はブログで@camloebaさんや@esumiiさんにデスクトップアプリケーションをOCamlで作成するように求めているようです。著者が挙げている「クリックカウンター」や「お絵かき」の類を作成するのに特別なexpertiseは不要で、単に使用するGUIライブラリ・グラフィックスライブラリのドキュメントが読めれば充分なので、その趣旨は私にはまったく判然としません(当該の記事で著者がOCamlで「お絵かきロジック」を実装してみせよと要求する一方で自身ではJavaScriptによるそれを公表していない点も気になります)。もちろん、OCamlであれHaskellであれ破壊的代入の類の副作用を使用せずに書くのもなんら困難ではありません。
クリックカウンターはHaskellでOpenGLに対するラッパーであるGlossライブラリを使うならばこうなります(手元の環境でインストールしやすかったのでこれを使いました):
import Graphics.Gloss.Interface.Pure.Game
main = play disp colour freq ini draw eTrans tTrans
where
disp = (InWindow "Click Counter" (225, 150) (40, 40))
colour = white
freq = 100
ini = 0
draw = Translate (-75) (-50) . Text . show
tTrans _ n = n
eTrans (EventKey (MouseButton LeftButton) Up _ _ ) n = n + 1
eTrans _ n = n
お絵かきアプリも難しいところはなにもなく、ドラッグ中かどうかのフラグをマウスボタンの上下で切り替えて管理し、フラグを状態遷移関数(状態とイベントをとって状態を返す純粋な関数)の引数として渡していくだけです。
import Graphics.Gloss.Interface.Pure.Game
import Data.Monoid(mconcat)
main = play disp colour freq ini draw eTrans tTrans
where
disp = (InWindow "Drawing Canvas" (400, 400) (40, 40))
colour = white
freq = 100
ini = (False, [], [])
draw (_, path, paths) = mconcat $ map line $ path:paths
tTrans _ w = w
eTrans (EventKey (MouseButton LeftButton) Down _ _ ) (_, path, paths) = (True, path, paths)
eTrans (EventKey (MouseButton LeftButton) Up _ _ ) (_, path, paths) = (False, [], path:paths)
eTrans (EventMotion pos) (True, path, paths) = (True, pos:path, paths)
eTrans _ w = w
いずれにせよ、状態機械の数学的構成では状態遷移が状態と入力から状態への関数として表現されるというだけのことではあり(状態渡し)、状態遷移が複雑になれば状態遷移を純粋な関数として表現する作業も複雑になり困難になるので(そしてそれはしばしば綺麗な関数合成では上手くいかないようなものになることが多いので)、結局のところ少なくとも現状のFRPも(イベントのシグナルから状態のシグナルを構成する際に状態遷移をそうした関数で表現する必要が出てくるので)本質的には銀の弾丸にはならないと言わなければなりません(関数プログラミングで書けるということの恩恵はもちろんあるとしても)。
同じです、Haskellで書こうと何しようと、一緒。
いずれにせよ、状態機械の数学的構成では状態遷移が状態と入力から状態への関数として表現されるというだけのことではあり(状態渡し)
みたいなことを、GUIアプリケーションのイベントを拾うために実装してはいけません。
マウスポインタの移動みたいな超頻繁なイベントが発生するこういう「お絵かきアプリ」なんて、即死確定ですが、普段関数型プログラミングでGUIアプリなんて書く経験が未熟なので、そんな基本的なことすら気にならないのでしょう。
要するにこういう連中は、関数型プログラミングの「理論」のことしか考えておらず、状態機械という言葉が使いまわせても、それが実用的にはメモリリークしてスタックオーバフローすることすら知らない
ということです。
あとね、
(当該の記事で著者がOCamlで「お絵かきロジック」を実装してみせよと要求する一方で自身ではJavaScriptによるそれを公表していない点も気になります)
こういう「気になります」程度の「揚げ足取り」部分をわざわざ太字にするとかしょうもないことしているから、各所で、レビューを装った単なるネガキャンにすぎない、だと見透かされているのだから留意したほうが良いです。
こちらは、自前で、FRPのライブラリを実装しているのだから、「お絵かきロジック」でもなんでも時間変化するアプリは書けるし、本書を読んではみたが、巧妙に論評を避けている(内容を理解できていない)Day5に「お絵かきロジック」と等価のReactサンプルアプリ書いているのですが、理解できなかったから無視したわけですね。
結論
住井 英二郎@esumiiさんとか、らくだの卯之助@camloebaさんに聞きたいのだけど、そもそも貴方たちはOCamlでデスクトップアプリ書けるんですか?
あんたらどうせ最終的には実用に耐える関数型プログラミングのコードなんて提供できないんでしょ?(笑)
という「確認」記事を出したところ、
案の定、メモリリークする実用に耐えない不良品しか提示されませんでした。
FRPを利用しない自称関数型GUIアプリは、メモリリークする不良品しか書けなかった
こういう連中の言うことを鵜呑みにしがちな【学習者要注意!】です。
というか、こういうのは序の口です。
この記事主はまだまだ、やらかしているので、それ全部反論していきます。
こんないい加減な知識とコードを書きなぐった記事で、人の主張を不当に否定して、学習者騙せて楽しいですか?いい加減にしたらどうか?
nonstarter 135 contribution May 31, 2015 11:27
hiyakashi_さん
マウスポインタの現在位置を表示するというような、先行するイベントに依存しない処理のみの場合には状態を渡して云々は特にありませんが、先行するイベントに依存するようなある程度複雑な処理をしようとすれば、どうしても状態とイベントから状態への純粋な関数を使って、シグナル(特にイベントのストリーム)を畳み込んで状態のストリームを得ていくことになりますよね(FRPを支援する代表的な関数型プログラミング言語Elmのfoldp : (a -> state -> state) -> state -> Signal a -> Signal stateがまさにそれです)。
ElmClickCounter.elm
clickCount : Signal Int
clickCount = foldp (\click total -> total + 1) 0 Mouse.clicks
一定以上に複雑なものを関数型スタイルで書こうとする場合には、状態遷移を純粋な関数として表現するという手法をFRPそれ自体によって免れることはできません(私が理解する限りでは)。
私の結論は、著者であるkenokabeさんの言葉を額面通り受け取れば、彼はFRPを理解していないのだろう(或いは少なくともfoldpのような畳込みが必要になるような一定以上に複雑なFRPを経験したことがないのだろう)、というものです。
GUIアプリケーションの構築に際して、IOモナドによるプログラミングがうまくいく/うまくいかないその程度に、現状のFRP(念頭においているのはElmですが)もうまくいく/うまくいかないだろう、と私は考えています。
また、
私の結論は、著者であるkenokabeさんの言葉を額面通り受け取れば、彼はFRPを理解していないのだろう(或いは少なくともfoldpのような畳込みが必要になるような一定以上に複雑なFRPを経験したことがないのだろう)、というものです。
とか、メモリリークする不良品コードをありがたがる人が、FRP理解していないとか因縁づけ。
Lambada 0 contribution May 31, 2015 15:37
岡部氏は
『関数型プログラミングのパラダイム内で、そういうマウスボタンが押されているのか、押されていないのか?入力の状態が時間遷移していく場合、上記ブログエントリでも論じたSICPでも、拙書でも解説しているとおり、FRPの実装が必ず必要となります。
「時間」が本質的だから、時間をを集合、SICPの言葉でいえば「遅延ストリーム」とした実装が必要です。』
と断言しているにも関わらず、現に私やnonstarter氏のコードのように
「時間」も「ストリーム」も出てこない関数的状態渡しによる実装が
容易である以上、岡部氏の誤りは明らかだと思います。ご参考まで。
念の為ですが、
拙書でも解説しているとおり、FRPの実装が必ず必要となります。
「時間」が本質的だから、時間をを集合、SICPの言葉でいえば「遅延ストリーム」とした実装が必要です。
っていう主張は普通に維持します。
断言しているにも関わらず、現に私やnonstarter氏のコードのように
「時間」も「ストリーム」も出てこない関数的状態渡しによる実装が容易である以上、岡部氏の誤りは明らかだと思います。ご参考まで。
はい確かに、メモリリークしてスタックオーバーフローするような「関数的状態渡し」=再帰関数の無限ループみたいなアホな実装は容易ですね。
もちろん、気の利いた技術者は普通にアホなやり方だと知っているのでやらないだけですが。
「理論」のことだけ考えて、実用的なGUIアプリケーションを書くことが念頭にないから、こういう低レベルなことを言って、間違っていない人を誤りが明らかだ、とかいう愚を犯す。
まだまだ続きます。
0 コメント:
コメントを投稿