天泣記

2009-05-04 (Mon)

#1

「抽象」に対して「捨象」という語がある。

英語ではどうなるかと思って辞典を引いてみたら、abstraction で同じだった。

2009-05-06 (Wed)

#1

wfo が qwik で動かなくなっていたのでいろいろ直す。

#2

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>'
#3

以下のように 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
#4

まぁ、URI が mutable であることがひとつの要因かなぁ。

immutable であれば、検査を緩めるのはオブジェクトの生成時だけで済んで @parser を導入する必要はなかったはずだ。

しかし現実には mutable であって、変更の度に検査するので、その検査のために @parser が必要になるのだろう。

2009-05-07 (Thu)

#1

chkbuild の diff で、長らく気になっていたのが、毎回順序が変わるケースである。

具体的には、

などがある。

順序が変わると diff がその変化を表示するのだが、それを見ても新たな知見が得られるわけではないので意味がない。

これをどうにかしたい (diff で表示させなくしたい) と前から思っていたのだが、どう対処すべきかという点が悩ましくてできていなかった。

たとえば、特定のプリフィクスを持つ行の連続を sort してしまうということが考えられるが、テストが失敗したりして diff に出てきてほしい時には sort してほしくない。

結局、前回と今回のログで、特定のプリフィクスを持つ行の連続が行の集合として等しければ sort する、ということにした。

テストが失敗したりすれば、行が変化して sort しなくなるはず、という仕掛けである。

#2

たとえば、以下のようなテキストで、先頭に同じ語が現れる連続した行の並びをその範囲でソートすることを考えよう。

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行しか短くならないか。

2009-05-08 (Fri)

#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 する。

だが、まとめかたがいくつもあるので、それを区別できない。

そうすると、名前を考えて新規メソッドだろうか。

2009-05-09 (Sat)

#1

値でまとまりを指示するのには gather という語を思いついて良さそうな気がするので提案した。[ruby-dev:38392]

先頭要素でまとまりを指示するのはいい名前が思いつかない。

2009-05-13 (Wed)

#1

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
#2

しかし、(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 }'

短くはならないか。

2009-05-14 (Thu)

#1

コンパクトに書くとこのくらいにはなるか。

% ./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

2009-05-19 (Tue)

#1

ふと、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 環境内には存在しないコマンドを実行できるかもしれない。

2009-05-21 (Thu)

#1

なんらかのバックアップをとるとして、最近のバックアップはたくさん残しておいて、古いバックアップはすこし残しておく、ということを考えたとしよう。

たとえば、毎日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番目を削除。

というようにやっていくと、期待を満たせるのではないか。

2009-05-22 (Fri)

#1

ふと、Google Trends で、

を調べると、ずっと低下傾向である。ふむ。

以下は微妙に増加傾向?

2009-05-24 (Sun)

#1

古いものをまばらにしていくバックアップのやりかたを図にしてみる。

% 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
}'
*
**
 **
 ***
 * **
   ***
   * **
   * ***
   * * **
   *   ***
   *   * **
       * ***
       * * **
       *   ***
       *   * **
       *   * ***
       *   * * **
       *   *   ***
       *   *   * **
       *       * ***
       *       * * **
       *       *   ***
       *       *   * **
               *   * ***
               *   * * **
               *   *   ***
               *   *   * **
               *       * ***
               *       * * **
               *       *   ***
               *       *   * **
               *       *   * ***
               *       *   * * **
               *       *   *   ***
               *       *   *   * **
               *       *       * ***
               *       *       * * **
               *       *       *   ***
               *       *       *   * **
               *               *   * ***
               *               *   * * **
               *               *   *   ***
               *               *   *   * **
               *               *       * ***
               *               *       * * **
               *               *       *   ***
               *               *       *   * **
                               *       *   * ***
                               *       *   * * **
                               *       *   *   ***
                               *       *   *   * **
                               *       *       * ***
                               *       *       * * **
                               *       *       *   ***
                               *       *       *   * **
                               *               *   * ***
                               *               *   * * **
                               *               *   *   ***
                               *               *   *   * **
                               *               *       * ***
                               *               *       * * **
                               *               *       *   ***
                               *               *       *   * **
                               *               *       *   * ***
                               *               *       *   * * **
                               *               *       *   *   ***
                               *               *       *   *   * **
                               *               *       *       * ***
                               *               *       *       * * **
                               *               *       *       *   ***
                               *               *       *       *   * **
                               *               *               *   * ***
                               *               *               *   * * **
                               *               *               *   *   ***
                               *               *               *   *   * **
                               *               *               *       * ***
                               *               *               *       * * **
                               *               *               *       *   ***
                               *               *               *       *   * **
                               *                               *       *   * ***
                               *                               *       *   * * **
                               *                               *       *   *   ***
                               *                               *       *   *   * **
                               *                               *       *       * ***
                               *                               *       *       * * **
                               *                               *       *       *   ***
                               *                               *       *       *   * **
                               *                               *               *   * ***
                               *                               *               *   * * **
                               *                               *               *   *   ***
                               *                               *               *   *   * **
                               *                               *               *       * ***
                               *                               *               *       * * **
                               *                               *               *       *   ***
                               *                               *               *       *   * **
                                                               *               *       *   * ***
                                                               *               *       *   * * **
                                                               *               *       *   *   ***
                                                               *               *       *   *   * **
                                                               *               *       *       * ***

2009-05-31 (Sun)

#1

ちょっと前に、[ruby-core:23543] で Kernel#in? が提案されていた。

まぁ、採用されそうにはないし、採用されるべきでもないとは思うが、その利点を言葉で説明することには興味がある。

おそらく、一言でいえば、英語で思考するプログラマにとっては、obj.in?(enum) は enum.include?(obj) よりも自然であるということなのだろう。

これはべつに馬鹿にしているわけではなくて、プログラムを書くことは思考をプログラムに翻訳することであり、プログラムを読むことはプログラムを思考に翻訳することである以上、そのように思考とプログラムを近づける工夫は使いやすさに対して重要で本質的なことなのだ。

まぁ、何にもまして重要というほどではないのだが。他のファクターもいろいろある。

ところで、こういうことをたまに考えたり書いたりするのだが、まとめておこうと思って「使いやすい言語・ライブラリをデザインするパターン」というのを始めてみた。

上述の Kernel#in? の話は DSL のところに例として書いてみたけれど、どうかなぁ。これひとつで DSL というのはちょっと違う気もするけれど。


[latest]


田中哲