「抽象」に対して「捨象」という語がある。
英語ではどうなるかと思って辞典を引いてみたら、abstraction で同じだった。
wfo が qwik で動かなくなっていたのでいろいろ直す。
URI は 1.9 で Marshal.dump したものを 1.8 では Marshal.load できないようだ。
% ruby -v
ruby 1.9.2dev (2009-05-02 trunk 23326) [i686-linux]
% ruby-1.8 -v
ruby 1.8.8dev (2009-04-22 revision 23257) [i686-linux]
% ruby -ruri -e 'Marshal.dump(URI("http://example.org"), STDOUT)'|
ruby-1.8 -ruri -e 'p Marshal.load(STDIN)'
-e:1:in `load': undefined class/module URI::Parser (ArgumentError)
from -e:1
-e:1:in `write': Broken pipe - <STDOUT> (Errno::EPIPE)
from -e:1:in `dump'
from -e:1:in `<main>'以下のように marshal_dump, marshal_load を加えて marshal しなおせば 1.8, 1.9 のどちらでも扱えるようになる。まぁ、parser の部分は忘れてしまうわけだが、今は不正な URI を扱う必要はないので気にしないことにする。
require 'uri'
module URI
def marshal_dump
self.to_s
end
def marshal_load(str)
if defined? DEFAULT_PARSER
@parser = DEFAULT_PARSER
end
replace! URI.parse(str)
end
endまぁ、URI が mutable であることがひとつの要因かなぁ。
immutable であれば、検査を緩めるのはオブジェクトの生成時だけで済んで @parser を導入する必要はなかったはずだ。
しかし現実には mutable であって、変更の度に検査するので、その検査のために @parser が必要になるのだろう。
chkbuild の diff で、長らく気になっていたのが、毎回順序が変わるケースである。
具体的には、
などがある。
順序が変わると diff がその変化を表示するのだが、それを見ても新たな知見が得られるわけではないので意味がない。
これをどうにかしたい (diff で表示させなくしたい) と前から思っていたのだが、どう対処すべきかという点が悩ましくてできていなかった。
たとえば、特定のプリフィクスを持つ行の連続を sort してしまうということが考えられるが、テストが失敗したりして diff に出てきてほしい時には sort してほしくない。
結局、前回と今回のログで、特定のプリフィクスを持つ行の連続が行の集合として等しければ sort する、ということにした。
テストが失敗したりすれば、行が変化して sort しなくなるはず、という仕掛けである。
たとえば、以下のようなテキストで、先頭に同じ語が現れる連続した行の並びをその範囲でソートすることを考えよう。
foo a foo c foo b bar z bar y bar z
そういう複数行にまたがる処理を行うには、ひとつの方法はログ全体をひとつの文字列に読み込んでしてしまうということが考えられる。それで、
str.gsub(/^(\w+).*\n(?:\1.*\n)*/) {
$&.lines.sort.join('')
}
などとするわけである。(おぉ、\1 の用途が)
が、ログはとても大きくなることがあるので、それはやりたくない。
そうすると 1行単位で処理していくわけだが、これがちょっと書きにくい。
まとまって処理される複数行の対象が認識できるのは、処理対象の後の行あるいは EOF を読んだときであり、IO#each_line を使って書くと、ブロックの中と、終わった後の両方に実際の処理が重複して現れる。
buf_word = buf_lines = nil
io.each_line {|line|
word = line[/\A\w+/]
if buf_word && buf_word != word
print buf_lines.sort.join('')
buf_word = buf_lines = nil
end
if !buf_word
buf_word = word
buf_lines = []
end
buf_lines << line
}
if buf_word
print buf_lines.sort.join('')
end
ここで、sort がブロックの中と外の両方に現れていてこれが気にいらない。
これは lambda で避けられる。
buf_word = buf_lines = nil
block = lambda {|line|
word = line ? line[/\A\w+/] : nil
if buf_word && buf_word != word
print buf_lines.sort.join('')
buf_word = buf_lines = nil
end
if word
if !buf_word
buf_word = word
buf_lines = []
end
buf_lines << line
end
}
io.each_line(&block)
block.call(nil)
しかし、考えてみると、each_line を使わなければ以下のようにかけるか。
buf_word = buf_lines = nil
begin
line = io.gets
word = line ? line[/\A\w+/] : nil
if buf_word && buf_word != word
print buf_lines.sort.join('')
buf_word = buf_lines = nil
end
if word
if !buf_word
buf_word = word
buf_lines = []
end
buf_lines << line
end
end while line
まぁ、1行しか短くならないか。
Enumerable を拡張すべきだろうか。
連続した要素をまとめて yield するメソッドは現在 Enumerable#each_slice があるが、これは固定個ごとにまとめるものなので、今回の用途には合わない。もっと柔軟なものが必要である。
いろいろと考えると、まとめる単位を指定する方法はいくつかあるような気がする。
とりあえず以下のものを思いついた。
たとえば、minitest の話であれば、
... MinitestSpec#test_needs_to_verify_kinds_of_objects: <elapsed> s: . MinitestSpec#test_needs_to_verify_non_nil: <elapsed> s: . MinitestSpec#test_needs_to_verify_floats_within_a_delta: <elapsed> s: . MinitestSpec#test_needs_to_verify_using_respond_to: <elapsed> s: . MinitestSpec#test_needs_to_catch_an_expected_exception: <elapsed> s: . MinitestSpec#test_needs_to_verify_equality: <elapsed> s: . ...
というようなところをソートしたいので、"MinitestSpec#" で始まる行の連続をまとめたい。そうすると、各行から "MinitestSpec#" みたいなところを取り出して、その取り出した値が同じになる行をまとめればいい。
また、空行で区切られたパラグラフをまとめたいなら、空行とそれ以外に分類して、それぞれをまとめればいい。
しかし、複数行をまとめる例をいろいろ考えていくと、必ずしもこればかりが良いやりかたではないようだ。
たとえば、ChangeLog をエントリ単位で分割するには、行頭が空白でない行を先頭として分割する。これをやるには、まとまりの先頭要素を検出する方法を指定するのが都合がいい。
また、C 言語のプログラムを関数単位に分割するなら、(乱暴だが関数が行頭の "}" で終了していると仮定して) まとまりの最終要素を検出する方法を指定するのが都合がいい。
一般にいえば、2行めを検出する方法を指定するとか、もっといろいろなやりかたがありえるが、それはフォーマット次第である。よく使いそうなのは他にあるだろうか。
また、どういうメソッドにするのがいいだろうか。
最初に考えたのは each_slice を拡張することである。整数のかわりに Proc を引数に指定すると、それをつかって要素をまとめて yield する。
だが、まとめかたがいくつもあるので、それを区別できない。
そうすると、名前を考えて新規メソッドだろうか。
値でまとまりを指示するのには gather という語を思いついて良さそうな気がするので提案した。[ruby-dev:38392]
先頭要素でまとまりを指示するのはいい名前が思いつかない。
struct の抽出 はこんなかんじですかね。
% ./ruby -rpp -e '
ARGF.gather {|line|
true if (/struct.*\{/ =~ line) .. (/\A\}/ =~ line)
}.each {|x| pp x }' include/ruby/ruby.h
["struct RBasic {\n", " VALUE flags;\n", " VALUE klass;\n", "};\n"]
["struct RObject {\n",
" struct RBasic basic;\n",
" union {\n",
"\tstruct {\n",
"\t long numiv;\n",
"\t VALUE *ivptr;\n",
" struct st_table *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */\n",
"\t} heap;\n",
"\tVALUE ary[ROBJECT_EMBED_LEN_MAX];\n",
" } as;\n",
"};\n"]
["typedef struct {\n",
" VALUE super;\n",
...
いや、連続してる場合もちゃんと区切るとこうか。
% ./ruby -v -rpp -e '
ARGF.gather(n: 0, f: false) {|line,h|
if /struct.*\{/ =~ line
h[:f] = true
h[:n] += 1
end
r = h[:f] && h[:n]
if /\A\}/ =~ line
h[:f] = false
end
r
}.each {|x| pp x }' include/ruby/ruby.hしかし、(lisp の) prog1 が欲しいよなぁ... ensure を使う? もちろん本当は ensure じゃなくて、単にブロックの値を決定した後に実行するコードを書くためのキーワードがいいのだけれど。
% ./ruby -v -rpp -e '
ARGF.gather(n: 0, f: false) {|line,h|
if /struct.*\{/ =~ line
h[:f] = true
h[:n] += 1
end
begin
h[:f] && h[:n]
ensure
if /\A\}/ =~ line
h[:f] = false
end
end
}.each {|x| pp x }'
長くなってるし。
もしかすると、ブロックで直接 ensure とかを使えたらいいのかなぁ。{ ... } だと変だけど、do ... end ならどうだろうか。
% ./ruby -v -rpp -e '
ARGF.gather(n: 0, f: false) do |line,h|
if /struct.*\{/ =~ line
h[:f] = true
h[:n] += 1
end
h[:f] && h[:n]
ensure
if /\A\}/ =~ line
h[:f] = false
end
end.each {|x| pp x }'
短くはならないか。
コンパクトに書くとこのくらいにはなるか。
% ./ruby -rpp -e '
ARGF.gather(n: 0) {|l,h|
h[:n] += 1 if h[:n].even? && /struct.*\{/ =~ l
h[:n] += 1 if (n = h[:n]).odd? && /\A\}/ =~ l
n.odd? && n
}.each {|x| pp x }' include/ruby/ruby.hふと、fexecve() を試してみる。
% cat t.c
#include <stdio.h>
#include <stdlib.h>
#define _GNU_SOURCE
#include <unistd.h>
extern char **environ;
int main(int argc, char *argv[])
{
fexecve(0, argv, environ);
perror("fexecve");
exit(1);
}
% gcc t.c
% ./a.out < =ls
a.out t.c u.c
標準入力につながっているコマンドを実行できている。
strace すると、/proc を使っているようだ。
% strace ./a.out < =ls
...
execve("/proc/self/fd/0", ["./a.out"], [/* 43 vars */]) = 0
...
ところで、exec した後、普通その fd は用なしだと思うのだが、close されるのだろうか?
% ./a.out -c 'lsof -p $$; :' < =sh COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME 0 22457 akr cwd DIR 3,1 4096 2023808 /tmp/f 0 22457 akr rtd DIR 3,1 4096 2 / 0 22457 akr txt REG 3,1 700492 962887 /bin/bash 0 22457 akr mem REG 3,1 99576 794854 /usr/lib/gconv/libJIS.so 0 22457 akr mem REG 3,1 7990944 799780 /usr/lib/locale/locale-archive 0 22457 akr mem REG 3,1 1413540 2371229 /lib/i686/cmov/libc-2.7.so 0 22457 akr mem REG 3,1 9680 2371234 /lib/i686/cmov/libdl-2.7.so 0 22457 akr mem REG 3,1 202188 2317527 /lib/libncurses.so.5.7 0 22457 akr mem REG 3,1 13568 787184 /usr/lib/gconv/EUC-JP.so 0 22457 akr mem REG 3,1 25700 783833 /usr/lib/gconv/gconv-modules.cache 0 22457 akr mem REG 3,1 113248 2319057 /lib/ld-2.7.so 0 22457 akr 0r REG 3,1 700492 962887 /bin/bash 0 22457 akr 1u CHR 136,6 8 /dev/pts/6 0 22457 akr 2u CHR 136,6 8 /dev/pts/6 0 22457 akr 5u CHR 136,6 8 /dev/pts/6
されないようだ。0 番は相変わらず実行したコマンド (この場合 bash) につながっている。
ではどうやったら close できるかちょっと考えると、close-on-exec を使えばできそうである。やってみるとうまく close できた。
% cat u.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define _GNU_SOURCE
#include <unistd.h>
extern char **environ;
int main(int argc, char *argv[])
{
int fd;
int ret;
fd = 0;
ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
if (ret == -1) { perror("F_SETFD"); exit(1); }
fexecve(fd, argv, environ);
perror("fexecve");
exit(1);
}
% gcc u.c
% ./a.out -c 'lsof -p $$; :' < =sh
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
0 22506 akr cwd DIR 3,1 4096 2023808 /tmp/f
0 22506 akr rtd DIR 3,1 4096 2 /
0 22506 akr txt REG 3,1 700492 962887 /bin/bash
0 22506 akr mem REG 3,1 99576 794854 /usr/lib/gconv/libJIS.so
0 22506 akr mem REG 3,1 7990944 799780 /usr/lib/locale/locale-archive
0 22506 akr mem REG 3,1 1413540 2371229 /lib/i686/cmov/libc-2.7.so
0 22506 akr mem REG 3,1 9680 2371234 /lib/i686/cmov/libdl-2.7.so
0 22506 akr mem REG 3,1 202188 2317527 /lib/libncurses.so.5.7
0 22506 akr mem REG 3,1 13568 787184 /usr/lib/gconv/EUC-JP.so
0 22506 akr mem REG 3,1 25700 783833 /usr/lib/gconv/gconv-modules.cache
0 22506 akr mem REG 3,1 113248 2319057 /lib/ld-2.7.so
0 22506 akr 1u CHR 136,6 8 /dev/pts/6
0 22506 akr 2u CHR 136,6 8 /dev/pts/6
0 22506 akr 5u CHR 136,6 8 /dev/pts/6
ここで 0 番は close されて表示されていない。
これを使うと chroot 環境内には存在しないコマンドを実行できるかもしれない。
なんらかのバックアップをとるとして、最近のバックアップはたくさん残しておいて、古いバックアップはすこし残しておく、ということを考えたとしよう。
たとえば、毎日1回バックアップをとるとして、最近1週間はすべて残しておき、最近1ヶ月は毎週月曜日のを残しておき、最近1年は毎月1日のを残しておく、といったことが考えられる。
残念なことに、上記の方法にはいびつなところがある。毎月1日は、月曜日とは限らない。そのため、毎月1日のバックアップは月曜日でなくても残しておかなければならない。月曜日以外の1週間経過したバックアップは消す、という処理はできない。
ということで、もっときれいな規則を定義できないかと以前から思っていた。期待する性質は以下のようなものである。
こういうアイデアはどうだろうか。
半分のバックアップは 2回生き残る。1/4 のバックアップは 4回生き残る。1/8 のバックアップは 8回生き残る。...
具体的な規則としては、バックアップに 1番から順に番号を振ったとして、n番目のバックアップをとったとき、n が 2進で xx..xx100..00 だったとき、yy..yy100..00 番のバックアップを削除する。ここで、xx..xx は 1 を最低ひとつ含む任意のビット列で、2つの 00..00 は同じ長さの 0 の並びである。そして、yy..yy は xx..xx から 1 を減じた値とする。
たとえば、奇数番目のバックアップ (1,3,5,...) を行ったときには、直前の奇数番目のバックアップを削除する。3番目のバックアップを作ったら 1番目を削除。
2の倍数番目のバックアップ (2,4,6,...) を行った時には、その系列における直前のバックアップを削除する。4番目のバックアップを作ったら 2番目を削除。
というようにやっていくと、期待を満たせるのではないか。
ふと、Google Trends で、
を調べると、ずっと低下傾向である。ふむ。
以下は微妙に増加傾向?
古いものをまばらにしていくバックアップのやりかたを図にしてみる。
% ruby -e '
def delnum(n)
bit = n & -n
return nil if n == bit
n - bit * 2
end
h = {}
N = 100
1.upto(N) {|i|
h[i] = true
h.delete delnum(i)
1.upto(i) {|j| print h[j] ? "*" : " " }
puts
}'
*
**
**
***
* **
***
* **
* ***
* * **
* ***
* * **
* ***
* * **
* ***
* * **
* * ***
* * * **
* * ***
* * * **
* * ***
* * * **
* * ***
* * * **
* * ***
* * * **
* * ***
* * * **
* * ***
* * * **
* * ***
* * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * ***
* * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***
* * * * * **
* * * * ***ちょっと前に、[ruby-core:23543] で Kernel#in? が提案されていた。
まぁ、採用されそうにはないし、採用されるべきでもないとは思うが、その利点を言葉で説明することには興味がある。
おそらく、一言でいえば、英語で思考するプログラマにとっては、obj.in?(enum) は enum.include?(obj) よりも自然であるということなのだろう。
これはべつに馬鹿にしているわけではなくて、プログラムを書くことは思考をプログラムに翻訳することであり、プログラムを読むことはプログラムを思考に翻訳することである以上、そのように思考とプログラムを近づける工夫は使いやすさに対して重要で本質的なことなのだ。
まぁ、何にもまして重要というほどではないのだが。他のファクターもいろいろある。
ところで、こういうことをたまに考えたり書いたりするのだが、まとめておこうと思って「使いやすい言語・ライブラリをデザインするパターン」というのを始めてみた。
上述の Kernel#in? の話は DSL のところに例として書いてみたけれど、どうかなぁ。これひとつで DSL というのはちょっと違う気もするけれど。
[latest]