ネコ情報
ネコを各種ブログ(Blog)から一括検索します。
トップ > google > google - 人気ブログ(Blog)検索結果詳細 (2008年12月4日 1時)
Luaのはまりどころ
マイクロスレッドの比較対象のひとつとしてlua5.1を使ってみたけど、Luaって最近の言語と比べて結構独特な部分が多いなと思いました。以下、はまったところ:
- Tableを配列風に使うときの開始インデックスは1であることを忘れる
- Tableの存在しないインデックスの値や未定義変数の値もnilが返る
- nilを値として使いにくい(ls = {nil, nil}; print(#ls) --=> 0)
- cls.method(self) と書くところを cls:method(self) と書いても、その間違いに気がつきにくい
- localを書き忘れる
coroutine用channel実装と、生産者消費者例
-- many-to-many channel Channel = {} function Channel:new() local obj = setmetatable({}, {__index = self}) obj:init() return obj end function Channel:init() self.paused_sender = setmetatable({}, {__index = table}) self.paused_receiver = setmetatable({}, {__index = table}) self.msg = nil end function Channel:send(msg) if self.msg ~= nil then -- pause sender when buffer queue filled up self.paused_sender:insert(coroutine.running()) coroutine.yield() end self.msg = msg if #self.paused_receiver > 0 then -- resume paused receiver local paused = self.paused_receiver:remove(1) -- lua index start with 1 coroutine.resume(paused) end end function Channel:recv() if self.msg == nil then -- pause receiver when buffer queue empty self.paused_receiver:insert(coroutine.running()) coroutine.yield() end local msg = self.msg self.msg = nil if #self.paused_sender > 0 then -- resume paused sender local paused = self.paused_sender:remove(1) coroutine.resume(paused) end return msg end -- examples prod = coroutine.wrap( function (ch) print("prod start") for i = 1, 10 do ch:send(i * 2) end ch:send(-1) print("prod end") end) cons = coroutine.wrap( function (ch) print("cons start") while true do local msg = ch:recv() if msg == -1 then break end print("rcv: " .. msg) end print("cons end") end) ch = Channel:new() prod(ch) cons(ch)
幾分冗長な実装です。yield/resumeではパラメータを受け渡し可能なので、工夫すればmsgは不要になるでしょう。queueも実質ひとつでよく、senderかreceiver化どちらかをため続けるようにすればいいはず。
作者:bellbind
更新日:2008年11月27日 18時55分
0usersってどういうことでしょうね
作者:bellbind
更新日:2008年11月25日 19時7分
新はてブの本文埋め込みがうざいので、Stylishで消してみる
新しいはてブは一覧ページでも本文を埋め込むようになったけど、どうもこの本文はノイズが多すぎて、一覧性にも支障があるので、Stylishを使ってユーザーCSSで消すことにしました。
タグで絞り込むと非表示表示を切り替えられるのに、絞り込まないとそれがないってのはおかしくないか?
ステータスバーのStylishアイコンをクリックして、「スタイルを書く」-「b.hatena.ne.jp用」を選び、CSSを以下のようにしました
@namespace url(http://www.w3.org/1999/xhtml); @-moz-document domain("b.hatena.ne.jp") { div.entry-body blockquote { display: none; } }
これですっきりしました。新デザインも意外とよくね?っと思ったけど、最初のエントリまでの段は1行で十分で3行もある(全部「注目」ばっか)のは余計だ。
ほかにもいろいろ問題あるけど、ベータテストで出てこなかったのかな。。。
作者:bellbind
更新日:2008年11月25日 18時51分
merbを使ってみる
rails風にwebアプリを作成できる話題のmerbを使ってみました。
インストール
すでにgem,rails,mongrelが入っているcygwin上にいれてみました*1。
まずはgemの更新
gem update gem clean
そしてmerbを入れる
gem install merb
入ったのはmerb-1.0.1でした。デフォルトでmerb_datamapper、do_sqlite3が入るようです。しかし、riの生成段階でmerb-slicesが失敗しました。
そこでmerb-slicesを削除して、rdoc生成しなおし
gem uninstall merb-slices gem rdoc --all
そしてmerb-slicesを入れなおす
gem install merb-slices
プロジェクト作成
インストールすると、コード生成用のmerb-genと実行用のmerbコマンドが入ります。
アプリケーション生成では、rails風なディレクトリ構成であるappと、より単純化したflatなどがあるようです。今回はappでやってみます。
merb-gen app myhello cd myhello
実行
merbコマンドでwebサーバが立ち上がります
merb
cygwinの場合、/home/xxxがrwxrwxrwxだとグループ書き込みが危険だよエラーが出て、停止します。そこでchmodしておき、再度merbを実行しました。
chmod 755 ~ merb
メッセージががーっとでてポート4000番で立ち上がったようです。
そこでブラウザでhttp://localhost:4000/にアクセスすると、「Flesh Merb App」ページが表示され、
Exception: No routes match the request: /
となってました。
いろいろ作成
最初のモデルオブジェクトとしてブックマークアプリ風なlinkをresourceで作ってみます。
$ merb-gen resource link
Loading ...
[ADDED] spec/models/link_spec.rb
[ADDED] app/models/link.rb
[ADDED] spec/requests/links_spec.rb
[ADDED] app/controllers/links.rb
[ADDED] app/views/links/index.html.erb
[ADDED] app/views/links/show.html.erb
[ADDED] app/views/links/edit.html.erb
[ADDED] app/views/links/new.html.erb
[ADDED] app/helpers/links_helper.rb
resources :links route added to config/router.rb
というファイルが追加されます。表示メッセージが色つきなのも面白い。
動かしてるmerbサーバも自動で適用するようで、そのままで http://localhost:4000/links/ にアクセスすると、index.html.erbの内容が表示されます( http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb みて編集しろという素っ気ないものですけど)。
model編集
resourceで生成されたlink.rbの内容は以下のようになってました。
class Link include DataMapper::Resource property :id, Serial end
url、titleとタイムスタンプを入れてみましょう。
- DataMapperのドキュメント: http://datamapper.org/doku.php
class Link include DataMapper::Resource property :id, Serial property :url, URI, :nullable => false, :writer => :protected property :title, Text, :nullable => false, :default => "no title", :lazy => false property :timestamp, DateTime, :nullable => false validates_is_unique :url end
単一モデルでなるべく制約を入れるようにやってみました。
ソースコードの更新は自動で反映するようですが、エラーが出るコードを保存してしまうと、merbは落ちるようです(svn upとかで更新する場合なら有効かも)。
対話環境でデータを入れてみましょう。
merb -i
webratを入れろと出て、終了しました。で、webratを追加しよう
gem install webrat
依存関係の中にネイティブライブラリnokogiriがあり、そのビルドにcygwinでは、libxml2-develとlibxslt-develが必要のようです(setup.exeで入れたらxorgまで入ってしまったorz)。
コンソールを立ち上げる前に一度dbを作ります。
rake db:create merb -i
以下のようにデータを作ってみます。
link = Link.new :url => "http://www.yahoo.com", :title => "Yahoo!", :timestamp => DateTime.now link.save link.id
idが振られていれば成功です。同じurlを持つ別のlinkを作ってsaveして失敗する(falseが返る)のを試してみたりました。
検索はidで検索する場合はget、一つだけならfirst、全部ならallです。
Link.get(1) Link.first(:url => "http://www.yahoo.com") Link.all(:url.like => "%yahoo%", :url.like => "%com%") # and 結合
Controller編集
ブラウザでこのLinkオブジェクトの表示や編集を可能にするために、erbファイルを修正することになります。
その前に、timestampの処理をcontrollerに追加しておきます。
生成されたデフォルトのコントローラlinks.rbは以下のようなものです
class Links < Application # provides :xml, :yaml, :js def index @links = Link.all display @links end def show(id) @link = Link.get(id) raise NotFound unless @link display @link end def new only_provides :html @link = Link.new display @link end def edit(id) only_provides :html @link = Link.get(id) raise NotFound unless @link display @link end def create(link) @link = Link.new(link) if @link.save redirect resource(@link), :message => {:notice => "Link was successfully created"} else message[:error] = "Link failed to be created" render :new end end def update(id, link) @link = Link.get(id) raise NotFound unless @link if @link.update_attributes(link) redirect resource(@link) else display @link, :edit end end def destroy(id) @link = Link.get(id) raise NotFound unless @link if @link.destroy redirect resource(:links) else raise InternalServerError end end end # Links
ここのcreateとupdateにlink.timestamp = DateTime.nowを入れました。
class Links < Application # provides :xml, :yaml, :js def index @links = Link.all display @links end def show(id) @link = Link.get(id) raise NotFound unless @link display @link end def new only_provides :html @link = Link.new display @link end def edit(id) only_provides :html @link = Link.get(id) raise NotFound unless @link display @link end def create(link) @link = Link.new(link) @link.timestamp = DateTime.now if @link.save redirect resource(@link), :message => {:notice => "Link was successfully created"} else message[:error] = "Link failed to be created" render :new end end def update(id, link) @link = Link.get(id) raise NotFound unless @link @link.timestamp = DateTime.now if @link.update_attributes(link) redirect resource(@link) else display @link, :edit end end def destroy(id) @link = Link.get(id) raise NotFound unless @link if @link.destroy redirect resource(:links) else raise InternalServerError end end end # Links
良くわからないけど、providesについてるコメントアウトもはずしました。
Viewの編集
erbファイルを、http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb を見ながら修正することになります。
erbファイルは4つ(index.html.erb, show.html.erb, new.html.erb, edit.html.erb)あり、最初から埋めておいてもいいんじゃね?という感じではありますが。
index.html.erb
<h1>Links controller, index action</h1> <table> <tr> <th>URL</th> <th>Title</th> <th colspan="3">Actions</th> </tr> <% @links.each do |link| %> <tr> <td><%=h link.url %></td> <td><%=h link.title %></td> <td><%= link_to 'Show', resource(link) %></td> <td><%= link_to 'Edit', resource(link, :edit) %></td> <td><%= delete_button(link, "Delete") %></td> </tr> <% end %> </table> <%= link_to 'New', resource(:links, :new) %>
show.html.erb
<h1>Links controller, show action</h1> <h3><%=h @link.title %></h3> <ul> <li><%=h @link.url %></li> <li><%=h @link.timestamp %></li> </ul> <%= link_to 'Back', resource(:links) %>
new.html.erb
<h1>Links controller, new action</h1> <%= form_for(@link, :action => resource(:links) ) do %> <p> <%= text_field :url, :label => "URL" %> </p> <p> <%= text_field :title, :label => "Title" %> </p> <p> <%= submit "Create" %> </p> <% end =%> <%= link_to 'Back', resource(:links) %>
edit.html.erb
<h1>Links controller, edit action</h1> <%= form_for(@link, :action => resource(@link) ) do %> <p> <%=h @link.url %> </p> <p> <%= text_field :title, :label => "Title" %> </p> <p> <%= submit "Update" %> </p> <% end =%> <%= link_to 'Back', resource(:links) %>
ブラウザで編集ができるようになりました。
感想
自動テストにあたるものがRSpecだったりと、railsとは若干セマンティクスが違う部分があります。けど、手順としては大体同じようにできるので、ドキュメントを探しながらやっていけば、railsの経験があればかんたんに使えるようになるのではないでしょうか(実際、上に上げた二つのドキュメントのリンクとrake --tasksくらいの情報だけでここまでできたし)。
ドキュメントに関しては、railsよりはよいのではないかなとも思います。とくにActiveRecordとくらべると、DataMapperはいろいろ探しやすい感じでした。
merbの機能としては、appのように作ってappに組み込むことができるようにするsliceとかあって、いろいろ面白そうです。
タグやコメント、アカウント管理のようなシステムはpluginとかでは(コードの更新等で)かなり苦しいのですが、sliceならこのあたりを含めてうまくできそうな気がします(本当にそうかは調べてないですけど)。
*1:cygwinでrailsに必要なパッケージは、ruby, gcc, make, swig, sqlite3, libsqlite3-develなど
作者:bellbind
更新日:2008年11月21日 9時48分
gccでのx86とamd64の時のsizeofの値
linux上でgcc-4.3を使った場合。違いはptr型、long、long double、size_t、time_tなどで起こる。
x86
sizeof(int) = 4 sizeof(void *) = 4 sizeof(size_t) = 4 sizeof(ptrdiff_t) = 4 sizeof(wchar_t) = 4 sizeof(void) = 1 sizeof(clock_t) = 4 sizeof(time_t) = 4 sizeof(FILE) = 148 sizeof(fpos_t) = 12 sizeof(div_t) = 8 sizeof(char) = 1 sizeof(short int) = 2 sizeof(long int) = 4 sizeof(long long int) = 8 sizeof(float) = 4 sizeof(double) = 8 sizeof(long double) = 12 sizeof(_Bool) = 1 sizeof(bool) = 1 sizeof(_Complex) = 16 sizeof(float complex) = 8 sizeof(double complex) = 16 sizeof(long double complex) = 24
amd64
sizeof(int) = 4 sizeof(void *) = 8 sizeof(size_t) = 8 sizeof(ptrdiff_t) = 8 sizeof(wchar_t) = 4 sizeof(void) = 1 sizeof(clock_t) = 8 sizeof(time_t) = 8 sizeof(FILE) = 216 sizeof(fpos_t) = 16 sizeof(div_t) = 8 sizeof(char) = 1 sizeof(short int) = 2 sizeof(long int) = 8 sizeof(long long int) = 8 sizeof(float) = 4 sizeof(double) = 8 sizeof(long double) = 16 sizeof(_Bool) = 1 sizeof(bool) = 1 sizeof(_Complex) = 16 sizeof(float complex) = 8 sizeof(double complex) = 16 sizeof(long double complex) = 32
ソース: sizeof.c
// gcc -std=c99 -Wall sizeof.c #include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <stdbool.h> #include <stdint.h> #include <complex.h> #include <wchar.h> #include <time.h> #include <fenv.h> #include <inttypes.h> #define size(type) printf("sizeof(" #type ") = %" PRIuPTR "\n", sizeof(type)) int main() { size(int); size(void *); size(size_t); size(ptrdiff_t); size(wchar_t); size(void); size(clock_t); size(time_t); size(FILE); size(fpos_t); size(div_t); size(char); size(short int); size(long int); size(long long int); size(float); size(double); size(long double); size(_Bool); size(bool); size(_Complex); size(float complex); size(double complex); size(long double complex); //size(_Imaginary); // not support on gcc-4.3 return 0; }
size_t型の値表示のprintfフォーマットコードは、inttypes.hのPRIuPTRを使うので正解らしい。
作者:bellbind
更新日:2008年11月19日 3時28分
つづき、文字単位コピーからブロック単位コピーにする
fgetcで書ければ、freadで書ける - ラシウラでは、fgetcからfreadを使うように変えました。これは機械的に変換しただけでしたが、ここからつぎは1文字づつコピーしている部分を、memcpyを使用して、ブロック単位でコピーするように変えてみましょう。
まずリファクタリング
まず、readlines中のバッファ確保の部分を関数として外に出し、readlinesをすっきりさせます。
char ** readlines(FILE * file) { enum {LINES_SIZE = 128}; enum {BUF_SIZE = 1024}; enum {CR = '\r'}; enum {LF = '\n'}; char ** lines = NULL; int lineindex = 0; int lines_size = LINES_SIZE; int index = 0; int line_size = 0; int is_prev_cr = 0; char buf[BUF_SIZE]; assert(file != NULL); lines = malloc(lines_size * sizeof(char *)); if (lines == NULL) goto error; lines[0] = NULL; for (;;) { int i = 0; int count = fread(buf, sizeof(char), BUF_SIZE, file); for (i = 0; i < count; i++) { char ch = buf[i]; if (is_prev_cr) { is_prev_cr = 0; if (ch == LF) continue; } if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!alloc_line(lines, lineindex, &line_size)) goto error; if (!expand_line(&lines[lineindex], index, &line_size)) goto error; if (ch == CR) is_prev_cr = 1; if (ch == CR || ch == LF) { realloc(lines[lineindex], (index + 1) * sizeof(char)); lineindex++; index = 0; } else { lines[lineindex][index] = (char) ch; index++; lines[lineindex][index] = '\0'; } } if (feof(file)) break; } realloc(lines, (lineindex + 2) * sizeof(char *)); return lines; error: freelines(lines); return NULL; }
expand_lines、alloc_line、expand_lineとして切り分けました。これらは以下のように実装です。更新する変数はそのポインタが渡るようにするくらいであり、をこれも機械的に行っています。
static int expand_lines(char *** plines, int lineindex, int * plines_size) { if (lineindex + 1 == *plines_size) { char ** expanded; *plines_size *= 2; expanded = realloc(*plines, *plines_size * sizeof(char *)); if (expanded == NULL) return 0; *plines = expanded; } return 1; }
static int alloc_line(char ** lines, int lineindex, int* pline_size) { enum {LINE_SIZE = 128}; if (lines[lineindex] == NULL) { *pline_size = LINE_SIZE; lines[lineindex] = malloc(*pline_size * sizeof(char)); if (lines[lineindex] == NULL) return 0; lines[lineindex + 1] = NULL; lines[lineindex][0] = '\0'; } return 1; }
static int expand_line(char ** pline, int index, int * pline_size) { if (index + 1 == *pline_size) { char * expanded; *pline_size *= 2; expanded = realloc(*pline, *pline_size * sizeof(char)); if (expanded == NULL) return 0; *pline = expanded; } return 1; }
ブロック単位コピー化する
バッファbufをブロック単位でコピーするためには、そのコピー範囲が必要です。
そこでループ中の処理は、コピー範囲(つまり、始点と終点)を探すものに変わります。
そして、いつコピーするか、を考える必要がでてきます。これは、改行が出たとき、そして、バッファの終端に届いたとき、の二箇所で行うことになります。
擬似コードでは、以下のようになるでしょう.
for (;;) { int 始点 = 0, 終点 = 0; count = fread(buf, sizeof(char), BUF_SIZE, file); for (i = 0; i < count; i++) { char ch = buf[i]; if (前がCR && ch == LF) { 始点、終点ともに1進む; continue; } if (ch == CR || ch == LF) { 現在行バッファを確保、拡張; 現在行バッファにbuf[始点...終点]をコピー; 次の行にいく; 終点が1進む; 始点 = 終点; } else { 終点が1進む; } } if (未コピー分がある場合) { 現在行バッファを確保、拡張; 現在行バッファにbuf[始点...終点]をコピー; } if (feof(file)) break; }
reallocでは、第一引数がNULLの場合、mallocと同じ動作になります。
前出のコードでは、行バッファは初期サイズから倍倍にしていくため、行バッファの確保と拡張を別々にしていました。
今回確保でも拡張でもコピーする分だけ確保できれば良く、確保すべき長さがわかっているため、確保も拡張もreallocで行うよう統一した、realloc_line関数で行うことにします。
readlines関数は以下のようになります。
char ** readlines(FILE * file) { enum {LINES_SIZE = 25}; enum {BUF_SIZE = 1024}; enum {CR = '\r'}; enum {LF = '\n'}; char ** lines = NULL; int lineindex = 0; int lines_size = LINES_SIZE; int index = 0; int is_prev_cr = 0; char buf[BUF_SIZE]; assert(file != NULL); lines = malloc(lines_size * sizeof(char *)); if (lines == NULL) goto error; lines[0] = NULL; for (;;) { int i = 0; int start = 0, end = 0; int count = fread(buf, sizeof(char), BUF_SIZE, file); for (i = 0; i < count; i++) { char ch = buf[i]; if (is_prev_cr) { is_prev_cr = 0; if (ch == LF) { end++; start++; continue; } } if (ch == CR) is_prev_cr = 1; if (ch == CR || ch == LF) { int len = end - start; if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!realloc_line(lines, lineindex, index + len + 1)) goto error; memcpy(&lines[lineindex][index], &buf[start], len); lines[lineindex][index + len] = '\0'; lineindex++; index = 0; end++; start = end; } else { end++; } } if (start != end) { int len = end - start; if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!realloc_line(lines, lineindex, index + len + 1)) goto error; memcpy(&lines[lineindex][index], &buf[start], len); lines[lineindex][index + len] = '\0'; index += len; } if (feof(file)) break; } realloc(lines, (lineindex + 2) * sizeof(char *)); return lines; error: freelines(lines); return NULL; }
static int realloc_line(char ** lines, int lineindex, int line_size) { char * buf = realloc(lines[lineindex], line_size * sizeof(char)); if (buf == NULL) return 0; lines[lineindex] = buf; lines[lineindex + 1] = NULL; return 1; }
バッファ拡張の部分は2箇所とも同じなので、同様に(copy_to_lineなどと名づけた関数にでも)一つにまとめれば、意外とすっきりしたコードになるでしょう。
いきなりこの関数実装をみてブロック処理がわからなくても、fgetcで一文字づつ処理する実装から変化させていくのを段階的に見ていけば、ブロック単位処理も難しいものではないことがわかるでしょう。
java.nioのBufferを使う処理も、このような処理方法で行うことになるでしょう。
put it together
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> char ** readlines(FILE *); void putlines(char **); void freelines(char **); int main(int argc, char ** argv) { FILE * file = NULL; char ** lines = NULL; char * filename = NULL; if (argc != 2) return EXIT_FAILURE; filename = argv[1]; file = fopen(filename, "r"); lines = readlines(file); fclose(file); putlines(lines); freelines(lines); return EXIT_SUCCESS; } static int realloc_line(char ** lines, int linecount, int line_size); static int expand_lines(char *** plines, int lineindex, int * plines_size); static int copy_line( char ** lines, int lineindex, int index, char * buf, int start, int end); char ** readlines(FILE * file) { enum {LINES_SIZE = 25}; enum {BUF_SIZE = 1024}; enum {CR = '\r'}; enum {LF = '\n'}; char ** lines = NULL; int lineindex = 0; int lines_size = LINES_SIZE; int index = 0; int is_prev_cr = 0; char buf[BUF_SIZE]; assert(file != NULL); lines = malloc(lines_size * sizeof(char *)); if (lines == NULL) goto error; lines[0] = NULL; for (;;) { int i = 0; int start = 0, end = 0; int count = fread(buf, sizeof(char), BUF_SIZE, file); for (i = 0; i < count; i++) { char ch = buf[i]; if (is_prev_cr) { is_prev_cr = 0; if (ch == LF) { end++; start++; continue; } } if (ch == CR) is_prev_cr = 1; if (ch == CR || ch == LF) { if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!copy_line(lines, lineindex, index, buf, start, end)) goto error; lineindex++; index = 0; end++; start = end; } else { end++; } } if (start != end) { if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!copy_line(lines, lineindex, index, buf, start, end)) goto error; index += end - start; } if (feof(file)) break; } realloc(lines, (lineindex + 2) * sizeof(char *)); return lines; error: freelines(lines); return NULL; } void freelines(char ** lines) { char ** cursor; if (lines == NULL) return; for (cursor = lines; *cursor; ++cursor) free(*cursor); free(lines); } void putlines(char ** lines) { char ** cursor; if (lines == NULL) return; for (cursor = lines; *cursor; ++cursor) puts(*cursor); } static int copy_line( char ** lines, int lineindex, int index, char * buf, int start, int end) { int len = end - start; if (!realloc_line(lines, lineindex, index + len + 1)) return 0; memcpy(&lines[lineindex][index], &buf[start], len); lines[lineindex][index + len] = '\0'; return 1; } static int realloc_line(char ** lines, int lineindex, int line_size) { char * buf = realloc(lines[lineindex], line_size * sizeof(char)); if (buf == NULL) return 0; lines[lineindex] = buf; lines[lineindex + 1] = NULL; return 1; } static int expand_lines(char *** plines, int lineindex, int * plines_size) { if (lineindex + 1 == *plines_size) { char ** expanded; *plines_size *= 2; expanded = realloc(*plines, *plines_size * sizeof(char *)); if (expanded == NULL) return 0; *plines = expanded; } return 1; }
余談: fread用バッファを用意せず、行バッファで読み込むようにする、と
freadから取り込むバッファを削ることも可能です。コピー回数とスタックサイズは減りますが、コードは若干複雑化し、おそらくバッファ確保回数も増えるので、パフォーマンスは落ちるでしょう。
char ** readlines(FILE * file) { enum {LINES_SIZE = 25}; enum {BUF_SIZE = 1024}; enum {CR = '\r'}; enum {LF = '\n'}; char ** lines = NULL; int lineindex = 0; int lines_size = LINES_SIZE; int index = 0; int is_prev_cr = 0; char * buf = NULL; assert(file != NULL); lines = malloc(lines_size * sizeof(char *)); if (lines == NULL) goto error; lines[0] = NULL; for (;;) { int i = 0; int start = 0, end = 0; int count; int buf_line_index; int buf_line_size; if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!realloc_line(lines, lineindex, index + BUF_SIZE)) goto error; buf_line_index = lineindex; buf_line_size = index; buf = &lines[lineindex][index]; count = fread(buf, sizeof(char), BUF_SIZE, file); for (i = 0; i < count; i++) { char ch = buf[i]; if (is_prev_cr) { is_prev_cr = 0; if (ch == LF) { end++; start++; continue; } } if (ch == CR) is_prev_cr = 1; if (ch == CR || ch == LF) { int len = end - start; if (buf_line_index != lineindex) { if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!realloc_line(lines, lineindex, index + len + 1)) goto error; memcpy(&lines[lineindex][index], &buf[start], len); } else { buf_line_size += len; } lines[lineindex][index + len] = '\0'; lineindex++; index = 0; end++; start = end; } else { end++; } } if (start != end) { int len = end - start; if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!realloc_line(lines, lineindex, index + len + 1)) goto error; memmove(&lines[lineindex][index], &buf[start], len); lines[lineindex][index + len] = '\0'; index += len; } if (buf_line_index != lineindex) { realloc(lines[buf_line_index], buf_line_size * sizeof(char)); } if (feof(file)) break; } realloc(lines, (lineindex + 2) * sizeof(char *)); return lines; error: freelines(lines); return NULL; }
これはやりすぎの例でしょう。
作者:bellbind
更新日:2008年11月12日 21時10分
Luaのはまりどころ
マイクロスレッドの比較対象のひとつとしてlua5.1を使ってみたけど、Luaって最近の言語と比べて結構独特な部分が多いなと思いました。以下、はまったところ:
- Tableを配列風に使うときの開始インデックスは1であることを忘れる
- Tableの存在しないインデックスの値や未定義変数の値もnilが返る
- nilを値として使いにくい(ls = {nil, nil}; print(#ls) --=> 0)
- cls.method(self) と書くところを cls:method(self) と書いても、その間違いに気がつきにくい
- localを書き忘れる
coroutine用channel実装と、生産者消費者例
-- many-to-many channel Channel = {} function Channel:new() local obj = setmetatable({}, {__index = self}) obj:init() return obj end function Channel:init() self.paused_sender = setmetatable({}, {__index = table}) self.paused_receiver = setmetatable({}, {__index = table}) self.msg = nil end function Channel:send(msg) if self.msg ~= nil then -- pause sender when buffer queue filled up self.paused_sender:insert(coroutine.running()) coroutine.yield() end self.msg = msg if #self.paused_receiver > 0 then -- resume paused receiver local paused = self.paused_receiver:remove(1) -- lua index start with 1 coroutine.resume(paused) end end function Channel:recv() if self.msg == nil then -- pause receiver when buffer queue empty self.paused_receiver:insert(coroutine.running()) coroutine.yield() end local msg = self.msg self.msg = nil if #self.paused_sender > 0 then -- resume paused sender local paused = self.paused_sender:remove(1) coroutine.resume(paused) end return msg end -- examples prod = coroutine.wrap( function (ch) print("prod start") for i = 1, 10 do ch:send(i * 2) end ch:send(-1) print("prod end") end) cons = coroutine.wrap( function (ch) print("cons start") while true do local msg = ch:recv() if msg == -1 then break end print("rcv: " .. msg) end print("cons end") end) ch = Channel:new() prod(ch) cons(ch)
幾分冗長な実装です。yield/resumeではパラメータを受け渡し可能なので、工夫すればmsgは不要になるでしょう。queueも実質ひとつでよく、senderかreceiver化どちらかをため続けるようにすればいいはず。
作者:bellbind
更新日:2008年11月27日 9時55分
0usersってどういうことでしょうね
作者:bellbind
更新日:2008年11月25日 10時7分
新はてブの本文埋め込みがうざいので、Stylishで消してみる
新しいはてブは一覧ページでも本文を埋め込むようになったけど、どうもこの本文はノイズが多すぎて、一覧性にも支障があるので、Stylishを使ってユーザーCSSで消すことにしました。
タグで絞り込むと非表示表示を切り替えられるのに、絞り込まないとそれがないってのはおかしくないか?
ステータスバーのStylishアイコンをクリックして、「スタイルを書く」-「b.hatena.ne.jp用」を選び、CSSを以下のようにしました
@namespace url(http://www.w3.org/1999/xhtml); @-moz-document domain("b.hatena.ne.jp") { div.entry-body blockquote { display: none; } }
これですっきりしました。新デザインも意外とよくね?っと思ったけど、最初のエントリまでの段は1行で十分で3行もある(全部「注目」ばっか)のは余計だ。
ほかにもいろいろ問題あるけど、ベータテストで出てこなかったのかな。。。
作者:bellbind
更新日:2008年11月25日 9時51分
merbを使ってみる
rails風にwebアプリを作成できる話題のmerbを使ってみました。
インストール
すでにgem,rails,mongrelが入っているcygwin上にいれてみました*1。
まずはgemの更新
gem update gem clean
そしてmerbを入れる
gem install merb
入ったのはmerb-1.0.1でした。デフォルトでmerb_datamapper、do_sqlite3が入るようです。しかし、riの生成段階でmerb-slicesが失敗しました。
そこでmerb-slicesを削除して、rdoc生成しなおし
gem uninstall merb-slices gem rdoc --all
そしてmerb-slicesを入れなおす
gem install merb-slices
プロジェクト作成
インストールすると、コード生成用のmerb-genと実行用のmerbコマンドが入ります。
アプリケーション生成では、rails風なディレクトリ構成であるappと、より単純化したflatなどがあるようです。今回はappでやってみます。
merb-gen app myhello cd myhello
実行
merbコマンドでwebサーバが立ち上がります
merb
cygwinの場合、/home/xxxがrwxrwxrwxだとグループ書き込みが危険だよエラーが出て、停止します。そこでchmodしておき、再度merbを実行しました。
chmod 755 ~ merb
メッセージががーっとでてポート4000番で立ち上がったようです。
そこでブラウザでhttp://localhost:4000/にアクセスすると、「Flesh Merb App」ページが表示され、
Exception: No routes match the request: /
となってました。
いろいろ作成
最初のモデルオブジェクトとしてブックマークアプリ風なlinkをresourceで作ってみます。
$ merb-gen resource link
Loading ...
[ADDED] spec/models/link_spec.rb
[ADDED] app/models/link.rb
[ADDED] spec/requests/links_spec.rb
[ADDED] app/controllers/links.rb
[ADDED] app/views/links/index.html.erb
[ADDED] app/views/links/show.html.erb
[ADDED] app/views/links/edit.html.erb
[ADDED] app/views/links/new.html.erb
[ADDED] app/helpers/links_helper.rb
resources :links route added to config/router.rb
というファイルが追加されます。表示メッセージが色つきなのも面白い。
動かしてるmerbサーバも自動で適用するようで、そのままで http://localhost:4000/links/ にアクセスすると、index.html.erbの内容が表示されます( http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb みて編集しろという素っ気ないものですけど)。
model編集
resourceで生成されたlink.rbの内容は以下のようになってました。
class Link include DataMapper::Resource property :id, Serial end
url、titleとタイムスタンプを入れてみましょう。
- DataMapperのドキュメント: http://datamapper.org/doku.php
class Link include DataMapper::Resource property :id, Serial property :url, URI, :nullable => false, :writer => :protected property :title, Text, :nullable => false, :default => "no title", :lazy => false property :timestamp, DateTime, :nullable => false validates_is_unique :url end
単一モデルでなるべく制約を入れるようにやってみました。
ソースコードの更新は自動で反映するようですが、エラーが出るコードを保存してしまうと、merbは落ちるようです(svn upとかで更新する場合なら有効かも)。
対話環境でデータを入れてみましょう。
merb -i
webratを入れろと出て、終了しました。で、webratを追加しよう
gem install webrat
依存関係の中にネイティブライブラリnokogiriがあり、そのビルドにcygwinでは、libxml2-develとlibxslt-develが必要のようです(setup.exeで入れたらxorgまで入ってしまったorz)。
コンソールを立ち上げる前に一度dbを作ります。
rake db:create merb -i
以下のようにデータを作ってみます。
link = Link.new :url => "http://www.yahoo.com", :title => "Yahoo!", :timestamp => DateTime.now link.save link.id
idが振られていれば成功です。同じurlを持つ別のlinkを作ってsaveして失敗する(falseが返る)のを試してみたりました。
検索はidで検索する場合はget、一つだけならfirst、全部ならallです。
Link.get(1) Link.first(:url => "http://www.yahoo.com") Link.all(:url.like => "%yahoo%", :url.like => "%com%") # and 結合
Controller編集
ブラウザでこのLinkオブジェクトの表示や編集を可能にするために、erbファイルを修正することになります。
その前に、timestampの処理をcontrollerに追加しておきます。
生成されたデフォルトのコントローラlinks.rbは以下のようなものです
class Links < Application # provides :xml, :yaml, :js def index @links = Link.all display @links end def show(id) @link = Link.get(id) raise NotFound unless @link display @link end def new only_provides :html @link = Link.new display @link end def edit(id) only_provides :html @link = Link.get(id) raise NotFound unless @link display @link end def create(link) @link = Link.new(link) if @link.save redirect resource(@link), :message => {:notice => "Link was successfully created"} else message[:error] = "Link failed to be created" render :new end end def update(id, link) @link = Link.get(id) raise NotFound unless @link if @link.update_attributes(link) redirect resource(@link) else display @link, :edit end end def destroy(id) @link = Link.get(id) raise NotFound unless @link if @link.destroy redirect resource(:links) else raise InternalServerError end end end # Links
ここのcreateとupdateにlink.timestamp = DateTime.nowを入れました。
class Links < Application # provides :xml, :yaml, :js def index @links = Link.all display @links end def show(id) @link = Link.get(id) raise NotFound unless @link display @link end def new only_provides :html @link = Link.new display @link end def edit(id) only_provides :html @link = Link.get(id) raise NotFound unless @link display @link end def create(link) @link = Link.new(link) @link.timestamp = DateTime.now if @link.save redirect resource(@link), :message => {:notice => "Link was successfully created"} else message[:error] = "Link failed to be created" render :new end end def update(id, link) @link = Link.get(id) raise NotFound unless @link @link.timestamp = DateTime.now if @link.update_attributes(link) redirect resource(@link) else display @link, :edit end end def destroy(id) @link = Link.get(id) raise NotFound unless @link if @link.destroy redirect resource(:links) else raise InternalServerError end end end # Links
良くわからないけど、providesについてるコメントアウトもはずしました。
Viewの編集
erbファイルを、http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb を見ながら修正することになります。
erbファイルは4つ(index.html.erb, show.html.erb, new.html.erb, edit.html.erb)あり、最初から埋めておいてもいいんじゃね?という感じではありますが。
index.html.erb
<h1>Links controller, index action</h1> <table> <tr> <th>URL</th> <th>Title</th> <th colspan="3">Actions</th> </tr> <% @links.each do |link| %> <tr> <td><%=h link.url %></td> <td><%=h link.title %></td> <td><%= link_to 'Show', resource(link) %></td> <td><%= link_to 'Edit', resource(link, :edit) %></td> <td><%= delete_button(link, "Delete") %></td> </tr> <% end %> </table> <%= link_to 'New', resource(:links, :new) %>
show.html.erb
<h1>Links controller, show action</h1> <h3><%=h @link.title %></h3> <ul> <li><%=h @link.url %></li> <li><%=h @link.timestamp %></li> </ul> <%= link_to 'Back', resource(:links) %>
new.html.erb
<h1>Links controller, new action</h1> <%= form_for(@link, :action => resource(:links) ) do %> <p> <%= text_field :url, :label => "URL" %> </p> <p> <%= text_field :title, :label => "Title" %> </p> <p> <%= submit "Create" %> </p> <% end =%> <%= link_to 'Back', resource(:links) %>
edit.html.erb
<h1>Links controller, edit action</h1> <%= form_for(@link, :action => resource(@link) ) do %> <p> <%=h @link.url %> </p> <p> <%= text_field :title, :label => "Title" %> </p> <p> <%= submit "Update" %> </p> <% end =%> <%= link_to 'Back', resource(:links) %>
ブラウザで編集ができるようになりました。
感想
自動テストにあたるものがRSpecだったりと、railsとは若干セマンティクスが違う部分があります。けど、手順としては大体同じようにできるので、ドキュメントを探しながらやっていけば、railsの経験があればかんたんに使えるようになるのではないでしょうか(実際、上に上げた二つのドキュメントのリンクとrake --tasksくらいの情報だけでここまでできたし)。
ドキュメントに関しては、railsよりはよいのではないかなとも思います。とくにActiveRecordとくらべると、DataMapperはいろいろ探しやすい感じでした。
merbの機能としては、appのように作ってappに組み込むことができるようにするsliceとかあって、いろいろ面白そうです。
タグやコメント、アカウント管理のようなシステムはpluginとかでは(コードの更新等で)かなり苦しいのですが、sliceならこのあたりを含めてうまくできそうな気がします(本当にそうかは調べてないですけど)。
*1:cygwinでrailsに必要なパッケージは、ruby, gcc, make, swig, sqlite3, libsqlite3-develなど
作者:bellbind
更新日:2008年11月21日 0時48分
gccでのx86とamd64の時のsizeofの値
linux上でgcc-4.3を使った場合。違いはptr型、long、long double、size_t、time_tなどで起こる。
x86
sizeof(int) = 4 sizeof(void *) = 4 sizeof(size_t) = 4 sizeof(ptrdiff_t) = 4 sizeof(wchar_t) = 4 sizeof(void) = 1 sizeof(clock_t) = 4 sizeof(time_t) = 4 sizeof(FILE) = 148 sizeof(fpos_t) = 12 sizeof(div_t) = 8 sizeof(char) = 1 sizeof(short int) = 2 sizeof(long int) = 4 sizeof(long long int) = 8 sizeof(float) = 4 sizeof(double) = 8 sizeof(long double) = 12 sizeof(_Bool) = 1 sizeof(bool) = 1 sizeof(_Complex) = 16 sizeof(float complex) = 8 sizeof(double complex) = 16 sizeof(long double complex) = 24
amd64
sizeof(int) = 4 sizeof(void *) = 8 sizeof(size_t) = 8 sizeof(ptrdiff_t) = 8 sizeof(wchar_t) = 4 sizeof(void) = 1 sizeof(clock_t) = 8 sizeof(time_t) = 8 sizeof(FILE) = 216 sizeof(fpos_t) = 16 sizeof(div_t) = 8 sizeof(char) = 1 sizeof(short int) = 2 sizeof(long int) = 8 sizeof(long long int) = 8 sizeof(float) = 4 sizeof(double) = 8 sizeof(long double) = 16 sizeof(_Bool) = 1 sizeof(bool) = 1 sizeof(_Complex) = 16 sizeof(float complex) = 8 sizeof(double complex) = 16 sizeof(long double complex) = 32
ソース: sizeof.c
// gcc -std=c99 -Wall sizeof.c #include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <stdbool.h> #include <stdint.h> #include <complex.h> #include <wchar.h> #include <time.h> #include <fenv.h> #include <inttypes.h> #define size(type) printf("sizeof(" #type ") = %" PRIuPTR "\n", sizeof(type)) int main() { size(int); size(void *); size(size_t); size(ptrdiff_t); size(wchar_t); size(void); size(clock_t); size(time_t); size(FILE); size(fpos_t); size(div_t); size(char); size(short int); size(long int); size(long long int); size(float); size(double); size(long double); size(_Bool); size(bool); size(_Complex); size(float complex); size(double complex); size(long double complex); //size(_Imaginary); // not support on gcc-4.3 return 0; }
size_t型の値表示のprintfフォーマットコードは、inttypes.hのPRIuPTRを使うので正解らしい。
作者:bellbind
更新日:2008年11月18日 18時28分
つづき、文字単位コピーからブロック単位コピーにする
fgetcで書ければ、freadで書ける - ラシウラでは、fgetcからfreadを使うように変えました。これは機械的に変換しただけでしたが、ここからつぎは1文字づつコピーしている部分を、memcpyを使用して、ブロック単位でコピーするように変えてみましょう。
まずリファクタリング
まず、readlines中のバッファ確保の部分を関数として外に出し、readlinesをすっきりさせます。
char ** readlines(FILE * file) { enum {LINES_SIZE = 128}; enum {BUF_SIZE = 1024}; enum {CR = '\r'}; enum {LF = '\n'}; char ** lines = NULL; int lineindex = 0; int lines_size = LINES_SIZE; int index = 0; int line_size = 0; int is_prev_cr = 0; char buf[BUF_SIZE]; assert(file != NULL); lines = malloc(lines_size * sizeof(char *)); if (lines == NULL) goto error; lines[0] = NULL; for (;;) { int i = 0; int count = fread(buf, sizeof(char), BUF_SIZE, file); for (i = 0; i < count; i++) { char ch = buf[i]; if (is_prev_cr) { is_prev_cr = 0; if (ch == LF) continue; } if (!expand_lines(&lines, lineindex, &lines_size)) goto error; if (!alloc_line(lines, lineindex, &line_size)) goto error; if (!expand_line(&lines[lineindex], index, &line_size)) goto error; if (ch == CR) is_prev_cr = 1; if (ch == CR || ch == LF) { realloc(lines[lineindex], (index + 1) * sizeof(char)); lineindex++; index = 0; } else { lines[lineindex][index] = (char) ch; index++; lines[lineindex][index] = '\0'; } } if (feof(file)) break; } realloc(lines, (lineindex + 2) * sizeof(char *)); return lines; error: freelines(lines); return NULL; }
expand_lines、alloc_line、expand_lineとして切り分けました。これらは以下のように実装です。更新する変数はそのポインタが渡るようにするくらいであり、をこれも機械的に行っています。
static int expand_lines(char *** plines, int lineindex, int * plines_size) { if (lineindex + 1 == *plines_size) { char ** expanded; *plines_size *= 2; expanded = realloc(*plines, *plines_size * sizeof(char *)); if (expanded == NULL) return 0; *plines = expanded; } return 1; }
static int alloc_line(char ** lines, int lineindex, int* pline_size) { enum {LINE_SIZE = 128}; if (lines[lineindex] == NULL) { *pline_size = LINE_SIZE; lines[lineindex] = malloc(*pline_size * sizeof(char)); if (lines[lineindex] == NULL) return 0; lines[lineindex + 1] = NULL; lines[lineindex][0] = '\0'; } return 1; }
static int expand_line(char ** pline, int index, int * pline_size) { if (index + 1 == *pline_size) { char * expanded; *pline_size *= 2; expanded = realloc(*pline, *pline_size * sizeof(char)); if (expand

