tamaño de fuente: + - prefijar
RSS 1.0
G a t a  D u r m i e n t e  -  d i a r i o
Volver al índice
Firefox :: Faster Safer Cuter
Say No to corrupt Audio CDs
三次元なんかに興味あるか、ばーか。

Tópicos Recientes

* まもなく移轉
當サイトはまもなく移轉します。

* みっくみく事件の決着
驚いた。ドワンゴが殆ど完全に折れる形で、しかもこんなに早く決着が付くとは思はなかった。こんな結末になるとは夢にも思はなかった。

* Quốc ngữ と日本語
ベトナム語は六つの聲調を持つ言語であるが、現在それを表記する爲に用ゐられてゐる Quốc ngữ ではその六声調を書き分ける事が出來るらしい。

* 自然物と信仰/生體機械としての人間/初音ミク/オープンソース戰爭
みっくみくが JASRAC された件には非常にもやもやとさせられる。だから出來るだけ消化しようと試みた。Every man thinketh his burden is the heaviest.

* HsHyperEstraier 0.1
HyperEstraier の Haskell 用バインディングである HsHyperEstraier を公開した。

Lee más...

_ viernes, 16 febrero 2007 I/O しない部分に親切にしてやること
Cuenta Larga = 12.19.14.1.5; tzolkin = 1 Chicchan; haab = 18 Pax [Trackback Ping]

 自分の (あづか) り知らぬ所で環境が勝手に變化すると、物事が複雑になる事はあっても單純にはならない。プログラム言語の最初期の段階に於ては、變數と言へばグローバル變數しか無かった。そこに Lisp がローカル變數を導入して、後に(多分 Smalltalk が)インスタンスプライベート變數を導入した。ローカル變數の目的は勿論、自分が計算に使ってゐる變數の内容がプログラムの他の部分の影響を受けないようにする事だし、インスタンスプライベート變數は他所に送った struct の中身を勝手に變へられないやうにする爲のものだ。それならば、變數の値を變更すると云ふ行爲そのものを規制するのも然程をかしな事ではなからう。實際 C++ の const 修飾子は良い線を行ってゐた。const 修飾された變數の値を變へようとするとコンパイルエラーになったし、const 修飾されたメソッドが自分のインスタンス變數の値を變へやうとするとコンパイルエラーになった。ただ惜しい事に const 修飾の無い部分についてはそこが副作用を起こし得るかどうかが不明だった。さうしなければ C のスーパーセットでなくなってしまふ。

 プログラムが本質的に副作用を避けられない場合と云ふものは、實はプログラムの外部との通信を行ふ場合しか無い。つまり自分を取り卷く環境から影響を受けたり環境に對して影響を與へる場合の事だが、それは例へばファイルを讀む事だったり、ソケットを開く事だったり、別のスレッドからも見えるメモリ上の場所を書き換へる事だったりする。その他の副作用は本質的には必然でなく、例へば整數の配列の中から値がゼロであるやうな要素を數へる時に普通は次のやうに書くかも知れないが、

unsigned count_zero_ordinarily(const int* vec, const unsigned len) {
unsigned i;
int ret = 0;

for (i = 0; i <= len; i++) {
if (vec[i] == 0) {
ret++;
}
}

return ret;
}
 かう書く事も可能だ。
unsigned count_zero_recursively(const int* vec, const unsigned len) {
if (len == 0) {
return 0;
}
else {
return (vec[0] == 0 ? 1 : 0)
+ count_zero_recursively(vec + 1, len - 1);
}
}
 後者の素敵な所は勿論、副作用が一切起こらない事だ。ループカウンタ i は存在しないし、中間結果を保持する ret も存在しない。それらは再歸される關數呼出しと關數呼出しの狭間に隠されてゐて表に出て來ない。後者の缺點は動作速度で前者に負ける事だが、それもこんな風に末尾再歸に變へてコンパイラに最適化させれば解決可能である。
unsigned count_zero_tail_recursively_aux(
const int* vec, const unsigned len, const unsigned result) {

if (len == 0) {
return result;
}
else {
return count_zero_tail_recursively_aux(
vec + 1, len - 1, result + (vec[0] == 0 ? 1 : 0)
);
}
}

unsigned count_zero_tail_recursively(const int* vec, const unsigned len) {
return count_zero_tail_recursively_aux(vec, len, 0);
}
 本質的な副作用と云ふものはそんなに多いだらうか。大抵のプログラムはファイルを讀み書きする事が主目的ではなく、從ってプログラム全體の中でファイルの讀み書きそのものが占めるコードの量はそんなに多くはない。さう云ふものは例へばカーネルとかドライバとかに限られるのではないか。カーネルは C で書けば宜しい。それが最も適切だらう。でもアプリケーションを書く爲の言語はもっと I/O しない部分に親切にしてやっても (ばち) は當たらない。そこで Haskell の出番だと云ふわけだ。

 良く『Haskell には副作用が無いから素晴しい』などと言はれるが、副作用の無い言語でコードを書いた事の無い人間にこれを言っても理解される筈が無いだらう。私にしても同じ事で、そんなものは Prolog と同じで現實には役に立たないだらうと思ってゐた。結局それは間違った思ひ込みだった。

でも I/O は大切だ。それを冷遇してはいけない

 勿論 I/O は本當に大切であり、實世界に於けるプログラムを I/O 抜きに語る事は出來ない。そんな事をすれば Prolog になってしまふ。或る概念が冷遇されてゐるかどうかを調べるのに最も良い方法は、その概念を實際に表現した時に、その部分がコード内に美しく自然に溶け込んでゐるかどうかを見る事だ。例へば、整數を二つ取る關數 add に引數を一つだけ部分適用して、その結果を別の關數 display に渡す事を考へる。display は受け取った關數に別の整數を適用し、結果を出力する。これを C++ で書くとこんな風になる。
#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;

static int add(int a, int b);
static void display(const function<int (int)>& f);

static int add(int a, int b) {
return a + b;
}

static void display(const function<int (int)>& f) {
cout << f(100) << endl;
}

int main() {
display(bind(&add, 50, _1));
return 0;
}
 關數ポインタの代はりに const function<int (int)>& を使って、關數呼出しの代はりに bind(&add, 50, _1) を使ふのだ。普通に add を呼ぶなら add(50, 100) なのに。boost は實に上手くやってゐる。魔法のやうに上手くやってゐるのだが、それが立ってゐる足場はもう boost::bind を冷遇してゐるどころではなくて、そもそも C++ が設計された當初に式テンプレートと關數ポインタと匿名名前空間とその他の色々のものが複雜に組み合はさった魔術で關數の部分適用なんかをやってのける變人が現れる事が想像されてゐただらうか。その變人にしても物理法則までは變へられなかった。だから boost::bind では多重定義された關數に對する部分適用は出來ない。不便な事だが、そのやうな關數へのポインタを取る方法が無いからだ。この問題の所爲で上手く行かなくなるのは部分適用だけではなく、關數呼出しを遲延させるやうな全ての動作にとって制約になる。例へば boost::lambda とか。

 ところで同じコードを Haskell で書くとこんな感じになる。あまりに自然に溶け込み過ぎて一體何處で部分適用が行はれたのか判らない程だ。
add :: Int -> Int -> Int
add a b = a + b

display :: (Int -> Int) -> IO ()
display f = putStrLn $ show $ f 100

main :: IO ()
main = display $ add 50
 次は I/O の例を擧げる。標準入力から一行ずつ讀んで、引數で與へられた單語を含んでゐる行だけを抜き出して、それらを行番號付きで標準出力に吐くやうな僞 grep を考へる。このプログラムが本質的に副作用を必要とするのは (1) プログラム引數の參照 (2) 標準入力からの讀込み (3) 標準出力への書き込みの三つだけだ。これは例へばこんな風に書ける。
import Control.Monad
import Data.List
import System.Environment

main :: IO ()
main = do word:[] <- getArgs
input <- getContents
mapM_ (putStrLn . format) $ (grep word . lines) input
where
format :: (Integer, String) -> String
format (num, line) = show num ++ ": " ++ line

grep :: String -> [String] -> [(Integer, String)]
grep word xs
= filter (\ (_, line)
-> line `contains` word) $ zip [1..] xs

contains :: String -> String -> Bool
contains world word
= any (\ tail
-> word `isPrefixOf` tail) $ tails world
 I/O してゐるのは main の定義の先頭三行だけだ。この三行は他の部分から浮いて見えるだらうか。私にはさう見えない。

2002
   10 11 12
2003
   1 2 3 4 5 6 7 8 9 10 11 12
2004
   1 2 3 4 5 6 7 8 9 10 11 12
2005
   1 2 3 4 5 6 7 8 9 10 11 12
2006
   1 2 3 4 6 7 8 9 10 11 12
2007
   1 2 3 4 5 6 7 8 9 10 12
2008
   1 4

login