nobsun: ここはHaskellプログラミングに興味をもつ人が,とりとめなく,お しゃべりをする場所です。 nobsun: またり部屋も用意してあります。> http://chaton.haskell.jp/matari/ nobsun: そろそろですね。 nobsun: 進行をどうするかちゃんと考えていませんが、「副作用」とその周辺の 話題について、とりとめなくやりたいとおもいます。 nobsun: 「副作用がなくてなぜゲームが書けるのか」というところからはじめま しょう。 fubabz: こんばんは kazu: こんばんは。 nobsun: こんばんは nobsun: この疑問を誘発しているのは「Haskellは副作用がない」という宣伝だ と思っているのですが。そうですか? kazu: そうです。 kazu: 宣伝している方の副作用の意味と、受け取っている方の副作用の意味がか み合っていません。 nobsun: で、そのとき、副作用=入出力という図式が受取側にあります? kazu: 受け側は、Haskell をあまり知らない、命令型プログラマー(OOを含む)だ と仮定して答えていいですか? nobsun: はいそうです。 kazu: 受け側の副作用とは、グローバル変数を変えたり(状態を持ったり)、入出 力したりといったことです。 nobsun: これは素朴な疑問なんですが、 fubabz: 破壊代入がない事を副作用がない,と思っているのではないですか kazu: それが状態って意味です。> fubabz さん nobsun: グローバル変数を変えることと、入出力とはどこで結び付いているので しょう。 nobsun: 状態がない=副作用がない? kazu: 命令型プログラマーの言う副作用とは、同じ引数を与えたときに、異なる 値を返したり、値を返すこと以外に仕事をしたりすることをいいます。 nobsun: 命令プログラマにとっても、値を返すことが計算の目的という感覚があ るんでしょうか? fubabz: 命令型にも関数はありますから,副作用の概念もあります kazu: いいえ。ただ、副作用のない関数は、同じ値を与えたときに、同じ値を返 し、しかもそれ以外のことはしないと教わります。 kazu: なので、printf なんかは、副作用(出力)があり、もっぱら副作用の方を 使うと説明します。 kazu: なので、printf に関して言えば、目的は副作用です。 nobsun: もし、printfが常に0を返す関数であっても、コンソールに変化がある ので副作用というわけですね。 kazu: そうです。 kazu: printf は、出力した文字数を返します。 kazu: printf("Hello\n") は常に6を返します。しかし副作用があります。 nobsun: ということは、副作用がないということと参照透明性とは別のこと。 fubabz: 自分はそう感じていました>nobsan kazu: nobsun さんのいう参照透明性の意味が分からないので、答えられません。 nobsun: ああ。わざと曖昧にしているのですが。逆に命令プログラマは参照透明 性ということを気にするものですか? fubabz: 参照透過性は純関数型プログラミングの基本なので,命令型の中にいる かぎりでは,気にしないですね metanest: 命令型の世界にいても、最適化で不変式としてループの外に出せるか どうかが違うから参照透過性は気にするかな? 私の場合 kazu: 僕の最近の結論を先に言わせて頂くと twitter に書いた通り、Haskell に副作用がないと表現するのは、命令型プログラマーにとっては誤りです。 kazu: Haskell には、入出力(という副作用)はあります。でも、状態(という副 作用)はありません。 nobsun: つまり、副作用はないけど、入出力は表現できるという表現は、良くな いということですか? kazu: Haskell の入出力という副作用を表現する型は IO です。 kazu: IO 型から純粋な関数は呼べますが、純粋な関数から IO は呼べませ ん。(unsafePerformIO は除く。) kazu: よって、Haskell では、入出力という副作用を持つコードと純粋なコード を完全に分離しています。ここが大切です。 kazu: 純粋な関数の部分は遅延するけど、IO の部分は順番が決まっています。 kazu: で、ここまで命令型プログラマーに分からせることができたら、状態がな いのにどうやってプログラムを書くのかということを分からせてあげれば いいと思います。 kazu: 発想を変えると状態がなくなる問題も多いし、引数に状態を渡して行け ば、グローバルな状態なんていらないよと。 kazu: 引数で与えるのが面倒なら、State モナドもあるよと。 nobsun: その場合、参照透明とはどういう意味で使っているのですか? nobsun: 遅延しても順序は決っているという説明ではだめですか。 kazu: 遅延しても順番は決まっているというのは、何を指してそういっているの ですか? > nobsun さん kazu: IO ですか? 純粋な関数ですか? fubabz: 順番を決定的にするための>>=の事じゃないでしょうか nobsun: IOと純粋な関数の区別をそもそもしていないんです。 nobsun: 値の依存関係を持ち込めば順序を指定できますよね。 nobsun: foo . bar nobsun: は、bar の計算が先で、fooがあと nobsun: foo (bar x) でもいいですが。 kazu: はい、できます。 kazu: 「遅延しても順序は決っているという説明」と言われましたが、多くの チュートリアルでは「遅延評価では順番は決まっていない」と説明され ていますね。 kazu: 遅延評価は順番が決まらないので入出力と相性が悪いと書かれています。 kazu: そもそも、この説明は間違いなのでしょうか? nobsun: 順序が決ってないのではなく、解らない、あるいは解りにくいという説 明になっていませんか? mad: 横から失礼します。こんばんは。 kazu: 解らないとは、どういう意味ですか? 決まっているなら、分かるでしょ う。。。 nobsun: 出現順序が計算順序を反映しないという説明ではないでしょうか。 mad: foo (bar x)では一般的にfooの評価が先です nobsun: fooの評価がさきですが、作用はfooが後です。 fubabz: 順番が決まらないと外界と作用できなくなるので,Haskellではモナド で解決したと理解しています kazu: fubabz さん、その理解が正しいとしても、その説明(これまでの典型的な 説明ですが)がよくないと思います。 kazu: 命令型の人には、さっぱり分からない説明になっています。 kazu: 繰り返しになりますが、Haskell には副作用を扱う型 IO があって、そこ は順番が決まっているという説明がいいと思います。 kazu: これならモナドを持ち出す必要はありません。 kazu: そして、謎は状態がないのに、どうやってプログラムを書くのだろうとい う一点になります。 nobsun: はい、そこへ誘導できるといいと思っています。わたしも。 fubabz: それで納得しますか? kazu: 多くの人は、それですんなり理解できると思いますよ。> fubabz さん fubabz: 自分は理解できなかったのでモナドにはまって苦しみました kazu: 納得できない人がいるとすると、これまでさんざんモナドとか言われたけ ど、理解できなかった人が、この期に及んでそんな説明をされても。。。 というパターンじゃないでしょうか? nobsun: Haskellerはなんとなく納得できないかも。 kazu: Haskeller は、どうして納得できないのですか? RWH でも、こういう説 明でしたよ。 nobsun: 「順番がきまっている」というのはIOの特権ではないからです。 nobsun: ああでもちがうな。 kazu: 遅延評価では順番が決まらないけど、入出力はIOモナドによって順番が決 まるって最初に説明をされたら、だれでも理解できず、モナドで苦しむの ではないでしょうか? > fubabz さん fubabz: 他の人はどうかわかりませんが,自分はできるだけ関数型らしく書きた いというのがあるので,モナドにこだわりました kazu: 僕は Lisp を 15 年ぐらいやってから Haskell に入りましたが、「関数 型らしい」とはまったく分かってなく、「関数型らしい」ということを 知りたくて Haskell を学び始めました。 nobsun: IOモナドと言わずに、IOとだけ言っとけばよかった。? kazu: はい。> nobsun nobsun: わたしも。そう思う。 kazu: モナドという言葉を出すなら、100ページぐらい紙面を取って、徹底的に 解説すべきです。 kazu: そうでないのであれば、モナドという言葉は出さない方がいいでしょう。 fubabz: まぁ,自分はそのひとりなので,今更ムダだといわれてもこまりますけど kazu: 「ムダ」って何を指していますか? > fubabz さん fubabz:「入出力はIOモナドによって順番が決まるって最初に説明をされたら、 だれでも理解できず、モナドで苦しむのではないでしょうか」←これを 私がやったのですが,否定されましたから fubabz: もっといい近道があればよかった,といういみです kazu: 僕も、同じ道でしたよ。 kazu: 他の人に同じ道を通れとは言いたくないのです。 kazu: それは、Haskell を理解しがたいものにしてしまいますし、ひいては Haskell の普及も阻害するような気がしています。 nobsun: でも、IOモナドをIOに変えてもあまり本質は変らなくないですか? kazu: 本質は説明の仕方を大胆に変えましょう。ということです。 kazu: Haskell には IO という型があって、それは副作用を扱う型で、順番が決 まっている。 kazu: そう言ってもらえるだけで、Haskell が分かった気分になれると思うのです。 nobsun: モナドという概念が余分に入る分、わかりにくくしているのか。 fubabz: そうですか.足をひっぱってすいませんでした.しばらくROMします kazu: いえ、いろいろ突っ込んで下さい。 > fubabz さん nobsun: つっこみよろしくです。> fubabz さん。 kazu: みんなが納得して、統一感のある説明をするようにしないと、Haskell が 普及しないと思うので。 kazu: ところで、nobsun さんは、Haskell は表現しているだけだから副作用が ないっていう発言をよくされますね? nobsun: 状態がないのに、なぜ入出力が表現できるか? nobsun: というところに移りましょうか。 kazu: 状態がないのに、なぜ入出力かではなく、状態がないのに、どうやってプ ログラムを書くのってことではないですか? nobsun: ああ、なるほど。 kazu: もう一度書きますが、ところで、nobsun さんは、Haskell は表現してい るだけだから副作用がないっていう発言をよくされますね? nobsun: はい。 kazu: もしそれが正しいなら、Java にも副作用はないと思います。Java でも何 がやりたいか、表現しているだけですよ。 nobsun: それは、ランタイムを考えていないからです。 nobsun: 私の場合は。 kazu: ランタイムを考えないなら、Java にも副作用はないですね? nobsun: いえ、変数の値を変えるという表現が含まれているので、副作用があり ます。 nobsun: 私にとって副作用は、同じスコープで変数の値を変更することです。 kazu: Hello World の Java プログラムにも副作用があるんですか? nobsun: Hello WorldのJavaのプログラムに副作用があるとは思いません。でも Javaという言語には副作用があると思っています。 nobsun: ああ。でも一貫していない気がしてきた。 kazu: nobsun に分かって頂きたいのは、Java の Hello World のプログラムに は、副作用がある(というか入出力という副作用しかない)と世間の人は 考えているということです。 kazu: そういう人たちに Haskell を普及させようという場合に、nobsun さんの 言葉は通じないと思います。 nobsun: そう思うようになりました。ので、今回のお題というわけです。 nobsun: :) kazu: 僕の観察したところでは、nobsun の副作用の意味は、世間の副作用の意 味とは違うので、nobsun さんは副作用という言葉を使わない方がいいの ではないでしょうか? kazu: 自分のいいたいことは、別の言葉を使って説明するといいでしょう。 kazu: 人に「Haskellには副作用があるのか?」と聞かれたら、「副作用の意味 が分からないので答えられない」というか、彼らの副作用の意味を受け 入れて「ある」と答えるかの、どちらかがいいと思います。 nobsun: はい。了解。 nobsun: おそらく。状態というところでも同じことがおこります。 kazu: Haskell には状態はないと思っていますが。。。 taninsw:こんばんわです kazu: こんばんは。 nobsun: 状態がないのに副作用があるんですか? nobsun: ああ。また副作用といっていまった。 taninsw:ランタイムがIOを扱うのを副作用と呼ぶのなら、ランタイムには状態 もあるのでは、とか。 kazu: 「状態」で起こりうる同じこととは何ですか? nobsun: 意味が一般の命令プログラマとは違うということです。 nobsun: おそらく。 kazu: 繰り返しになりますが、一般の命令プログラーが言う副作用とは1)入出力 と2)状態です。 nobsun: はい。 kazu: Haskell は 1) はありますが、2) はありません。と僕は思っています。 kazu: nobsun さんの状態という意味が、他のプログラマーの状態と違うとは思 えないのですけれど。。。 taninsw:Stateモナドがあるのに状態がないとはどういうことか、とか? kazu: State モナドは、単に裏で引数を渡しているだけですね。> taninsw さん taninsw:それはそうですが、状態という概念を扱ってるのには変わりはない、 と見る事もできる、と思います。 kazu: 状態はないけど、状態をエミュレートできると僕は説明します。> taninsw さん nobsun: そうかもしれない。 nobsun: 状態は表現できますが、状態はありません。という言い方はどうですか? kazu: あー、出た。「表現」! nobsun: ああ。これか。 kazu: 命令型プログラマーは、たとえば for 文がないのにどうやってループが 書けるの? と思っています。 kazu: これに答えるのは簡単ですよね? nobsun: プログラムの話と、プログラム言語というメタな話が混乱しているかも。 taninsw:言語仕様として状態は無いが、エミュレートする事で、状態を扱え る、という理解です。前者の状態は「識別子と値の束縛」という狭義 の意味で、後者の状態は、設計的に見た広義のものです。 nobsun: 状態がなくてプログラムが書けるか?は前者の意味ですよね。 kazu: そうでしょう。> 前者 nobsun: 状態とは変化するものですよね。 taninsw:だと思います。ただHaskell初心者は誤解するかも、と。 kazu: そうです。 nobsun: ということは、変化しないものとの対比がある概念ですよね。 kazu: そうです。 nobsun: もし、束縛関係が変化しないなら、状態はないといっていいですよね。 kazu: いいんじゃないですか? nobsun: 重箱のすみですが、「識別子と値の束縛」は変化がないなら、状態とは 言えない。 nobsun: ああ。ほんとに重箱のすみだった。 taninsw:前者の定義にのっとればそうだと思います kazu: 命令型の人には、発想を変えると状態がなくても多くの問題は解けること と、どうやって状態をエミュレートするかってことを教えてあげればいい と思うです。 kazu: Haskell で 2 年ぐらいプログラムを書き続けていると、本質的に状態を 持つ問題の例を挙げる方が難しくなってきます。不思議だ。 nobsun: そうだと思います。 nobsun: 状態がないので、そもそも問題を状態(の変化)として捉えることがなく なるんですよ。 kazu: だから、State モナドの利用例を説明するのが難しくなります。。。 nobsun: つまり、状態がないのにどうやってプログラムするという疑問を解決で きたというより、そもそも状態を使って解決するという発想がなくなる んです。 taninsw:自分は万年初心者なので分からないのですが、どういうエミュレート のパターンがありますかね。再帰で状態変数を持たせて回す? nobsun: いえ、単に引数と、結果に状態を追加するだけです。 taninsw:GUIアプリケーション等も自然にそう設計できるようになりますか? kazu: 僕がエミュレートといったのは State モナドのことです。 kazu: 現状では、GUI プログラムを書く場合、C ライブラリにバインドするの で、C ライブラリが状態を仮定している以上、Haskell でも State モナド を使うことになると思います。 taninsw:なるほど kazu: Yi っていう Haskell で書かれたエディタがあるのですか、これはどう やって実装されているのでしょうね。 kazu: maoe さんが興味を示されている FRP を学ぶと、IO が絡んでも、もっと 関数的に書けるんでしょうか? kazu: http://d.hatena.ne.jp/maoe/20091121/1258773681 kazu: たとえば、for がなくてプログラムが書けるの?っていう質問への答え は、ここに書いています。 kazu: http://www.mew.org/~kazu/material/2008-haskell.pdf heita: 今日は人が少ないのかな? nobsun: よりfunctionalにというのは、どういう感覚ですか? kazu: その感覚を身につけたいと思っているだけなので、習得できてない今、ど ういったものか想像も付きません。 kazu: ただ、The Haskell School of Expression は読みましたので、その延長 上にあるとすると、宣言的な DSL なのかなぁとは思います。 heita: Erlangのプログラムみたいに、メッセージとそれに対応する処理だけを 記述するような DSL っぽいものがあって、IO と処理記述が分離される ような構造になるのがいいと思う。ちょっと、実際に書いてみないと、 うまくいくかどうか判らないけど。 kazu: ちなみに、Haskell でネットワークのコードを書いていると、do ばかり で、命令型言語と変わらないなぁという感じです。。。^^; heita: liftIO を頻繁に挟み込んだり、unsafePerformIO しなくて、という意味 だと推察…… kazu: あー。僕は最近 IO に関して悟りを開いたので、unsafePerformIO は要ら なくなりました。> heita さん kazu: この悟りは、難しくて説明できません。 nobsun: そこをなんとか、言葉に。 kazu: うーん。たとえば、サーバーを書くとします。 kazu: 通常のモードでは syslog に、デバッグモードでは stderr にログを吐き たいと思うでしょう。 nobsun: はい。 kazu: main では、"-d" オプションを optDebug みたいなものにパースして、 これをグローバル変数として読みたくなるので、unsafePerformIO とか 使ちゃってました。 kazu: optDebug を参照しているところでは、if optDebug then return () else initSyslog とか、if optDebug then hPutStr xxx else syslog xxx とかしたくなるわけです。 kazu: でも、IO はファーストクラスなので、main のみで optDebug を参照し、 そこで Config を作成できます。Config { init = initSyslog, writeLog = syslog } とかです。 kazu: これを渡して行けば、optDebug を下位の関数で参照することはなくなり ます。 kazu: こういうことを突き詰めて行くと、うまく説明できませんが、深く Config を渡す必要はなくなったので、unsafePerformIO は要らなくなり ました。 nobsun: MonadReader ? kazu: ReaderT と IO を合成すると、liftIO ばかりになって、プログラムを書 くのが嫌になります。なので、使いません。 nobsun: 明示的に渡す?Config kazu: あくまで、Config を必要な関数の引数として渡しているだけです。で も、関数Aが必要とする情報はこれ、関数Bが必要とする情報はあれ、 という風に分割できるようになるので、深く渡す必要がなくなって (ここはうまく説明できない)、コマンドライン引数を全体から参照する 必要はないやって、気分になれました。 kazu: あー、なんか説明できる気がしてきた。 kazu: 今までのサーバーのコードは、main から深く深く命令が連なっていまし た。それを、ライブラリにできるところは、すべてライブラリにしたん ですよね。 kazu: すると、main では、ライブラリに渡す IO を組み立てて、そのライブラ リに渡すことになります。 kazu: 今までは、ライブラリの先に、その IO があった訳ですが、今はライブラ リと、その IO は同じレベルにあります。だから、ライブラリに ConfigA と、ConfigB を部分適応した IO を渡すので、ConfigX を深く渡している 気分じゃなくなったのでした。 kazu: 分かるかなぁ? nobsun: むむ。 nwn: 部分適応 nwn: ってなんでしょうか sakai: 部分適用のことでは。 kazu: runNetworkServer :: Config -> IO () -> IO () という汎用的なネット ワークサーバーライブラリがあるとします。(select() の壁を越えるため の。) kazu: で、runWebServer :: WebConfig -> (Request -> IO Response) -> IO () という Web サーバーライブラリがあるとします。 kazu: で、webServer :: Request -> IO Response という Web サーバーを書い たとします。(200 とか 404 とか返すコード) kazu: こうしておくと、main から kazu: runNetworkServer cnf (runWebServer webCnf webServer) kazu: とか書けるんですよね。 kazu: cnf と webCnf は、コマンドライン引数から main で作成します。 kazu: こういう風にライブラリ化してないときは、runNetworkserver の向こう に runWebServer があり、深くて、引数を渡して行くのは嫌だなぁと思って いました。 kazu: 今は、main から両方にほいって渡すだけって気分になれました。 kazu: こんな感じなんですが、分かります? nobsun: その悟りを得たあとから、元のunsafePerformIOを使いたかったときの 感覚はどういうものに見えますか? kazu: IO がファーストクラスという意味と、高階関数の使い方を分かっていな いやつだった。ですかね? heita: 外界(config)に依存していた関数を、引数で渡される情報(config)だけ に依存する純粋な関数に書きかえたってことですか。 kazu: そうですが、高階関数で深い部分を浅瀬に引き上げたとうことです。> heita さん kazu: その内、今作っている Web サーバーは公開します。 kazu: www.mew.org の Apache を置き換えた後になりますけどね。 heita: おー、mew.orgを支えられる品質のものになるのですか。すごい。 kazu: 速度的には、Apache と遜色内ですよ。ベンチマークによれば。。。 kazu: 今は、必要な機能を付けて行っているところです。 kazu: 話題を変えてもいいですか? nobsun: はい。 heita: 副作用に? kazu: 関数プログラマーが言う、参照透明性と effect の意味を教えて下さい。 heita: おー、失礼。 heita: 参照透明性は、変数の状態(bind)が常に変わらないこと(?) nobsun: 参照透明性はすこしわかりにくいです。本来の意味は、「等しいものを 等しいもので置き換えても意味は変らない」ということをなりたたせる 条件です。 nobsun: 参照透明の議論をしたペーパーがあったのですが、sakaiさんが読んで いたとおもいます。なんというペーパーでしたっけ > sakai さん nobsun: Harald Søndergaard and Peter Sestoft sakai: 参照透明の議論をした論文のURLを一応はっておきます。 http://dx.doi.org/10.1007/BF00277387 nobsun: effect の方は私もよく掴めていません。外界への影響という意味か なぁと思っていますが。。。 sakai: effectはちゃんとした定義は知らないけど、値以外の意味の総称だと 思ってます。 nobsun: 「値」以外の「意味」ということは、Oprationalな意味ということです かねぇ。 kazu: 値以外には、たとえば何があるのですか? > effect nobsun: 副作用!? sakai: 副作用とか非決定性とか。 sakai: 非停止性なんかもeffectと考えることあり。 sakai: それから、例外とか大域脱出とかもか。 nobsun: ふむふむ control か。 nobsun: では、effectual computation というのは? sakai: effectual computation は文字通り effect を持つような計算じゃない のかな。 sakai: どっかででてきた用語? nobsun: Applicative の記事 sakai: あ、Chatonにあるこれか。 nobsun: そう。 sakai: やっぱり、effectを持つような計算でいいと思う。 nobsun: ふむふむ。 sakai: ただ、あまり気にする必要はないとは思うけど、computationは少し特殊 なニュアンスがあるかも。 sakai: Moggi の Computational lambda-calculus and monads あたりの話では value と computation の区別が大事で、そこでの意味が念頭にあると 思うので。 nobsun: WadlerとかもMonadはComputationを表現するというようなことを最初 いっていたのはそこが大元ですかね。 sakai: うん。Moggiが大元だと思っていいと思う。 sakai: 一応、URLを。 sakai: http://www.disi.unige.it/person/MoggiE/ftp/lics89.pdf sakai: http://www.disi.unige.it/person/MoggiE/ftp/lc88.pdf nobsun: ありがとうございます。 sakai: まったくもって、プログラマ向けではないけれど…… nobsun: さて、22時になりました。ひとまず HIMA 3終了を宣言しておきます。 この場所は明早朝5時ごろまでは、このままにしておきます。 参加していただいたみなさまありがとうございます。 ROMのみなさまもありがとうございます。 sakai: お疲れ様でした~ nwn: お疲れさまでしたー。 shelarcy:おつかれさまでした。 taninsw:おつかれさまでしたー kazu: 遅くなりましたが、お疲れさまでした。