トップ 最新 追記

Route 477



2007-11-01

[junk] 風邪

最近更新がなかったのは風邪気味だったからで、風邪を引くと判断力が低下するので つい夜更かししてしまい、更に風邪が悪化するという悪循環。

しかし副作用として、DTD HARDでハイスコアが出た(9700)。

[javascript] mootoolsのダウンロード版ドキュメントを作った

ネット繋がってないとリファレンス引けないのは不便だよねー、ということで。

[ruby] 大量のhtmlのリンクを一瞬で張り直すRubyスクリプト

ごめん一瞬は嘘かも。8瞬くらいで。

上のmootoolsのリファレンスを作るためにとりあえずhtmlを全部ダウンロードしたんだけど *1、拡張子が全部jsになってたり、cssが絶対パスになってたりで このままでは使えない。

というわけで、フィルタを適用しながらディレクトリを複製するRubyスクリプトを書いてみた。

#
# filter_mirror.rb
#
class FilterMirror
  def self.run(src, dst, filter = lambda{|f, d| [f, d]})
    new(src, dst, filter).run
  end

  def initialize(src, dst, filter)
    @src_path, @dst_path, @filter = src, dst, filter
  end

  def run
    Dir.glob("#{@src_path}/**/*").each do |from|
      to = from.dup
      to[/\A#{Regexp.quote(@src_path.to_s)}/] = @dst_path.to_s

      if File.directory?(from)
        to, data = @filter.call(to, "")
        Dir.mkdir(to)
      else
        data = File.open(from, "rb"){|f| f.read}
        to, data = @filter.call(to, data)
        File.open(to, "wb"){|f| f.write data}
        puts "wrote #{to}"
      end
    end
  end
end

呼び出し元はこちら。

#
# mootools_relink.rb
#
require 'rubygems'
require 'hpricot'
require 'pathname'
require 'filter_mirror.rb'

# フィルタ

filter = proc{|path, data|
  # ファイル名の修正
  unless path =~ /scripts/
    path.sub!(/\.js\z/, "\.html")  # *.jsを*.htmlにリネーム
  end
  path.sub!(/css\....\.css/, "css") # *.css.xzc.css → *.css

  # ファイル内容の修正
  if path =~ /\.html\z/
    is_index = (path =~ /index\.html\z/)

    doc = Hpricot(data)
    # 全てのaタグに対し
    (doc/:a).each do |a|
      if a["href"]
        a["href"] = a["href"].sub(/\.js/, "\.html")           # *.jsへのリンクを*.htmlに
        a["href"] = a["href"].sub(%r{\A../}, "") if is_index  # index.htmlなら ../ を除去
      end
    end
    # 全てのlinkタグに対し(css)
    (doc/:link).each do |link|
      link["href"] = link["href"].sub(%r{\A/styles/}, is_index ? "styles/" : "../styles/")
    end
    # 全てのscriptタグに対し(javascript)
    (doc/:script).each do |script|
      script["src"] = script["src"].sub(%r{\A/scripts/}, is_index ? "scripts/" : "../scripts/")
    end
    data = doc.to_html
  end

  return [path, data]
}

# メイン

if ARGV.size < 2
  puts "usage: #{$0} from to"
  exit
end
src_path = Pathname.new(ARGV[0])
dst_path = Pathname.new(ARGV[1])
if dst_path.exist?
  puts "error: #{dst_path} already exists"
  exit
end
dst_path.mkdir

FilterMirror.run(src_path, dst_path, filter)

フィルタはファイルのパス名とファイルの中身を引数に取り、それらの配列を返す。 変更しない場合はそのまま返せばいい。ここではHpricotを使ってリンクの張り直しをしています

Hpricotについてはこちらをどうぞ。

*1 wgetなりgethtmlwなりで

[ruby] procとProc.new

前者はreturnできるが後者はreturnできないらしい?

(11/2追記:http://d.hatena.ne.jp/ha-tan/20071031/1193883317 で書かれてました。return先が違うのね。)

[Plagger] Publish::HTMLというプラグインを書いてみた

みんなEFTのテストとかどうやってるんですかね?いちいちGMailに送って確かめる、ってのも面倒すぎるよなぁ。

というわけで、Perlの練習がてらにエントリをHTMLに出力するプラグインを書いてみたよ。

package Plagger::Plugin::Publish::HTML;
use strict;
use warnings;
use base qw( Plagger::Plugin );

our $VERSION = '0.01';

use File::Spec;
use IO::File;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.feed' => \&feed,
    );
}

sub feed {
    my ($self, $context, $args) = @_;

    # open file
    my $dir = $self->conf->{dir};
    unless (-e $dir && -d _) {
        mkdir $dir, 0755 or $context->error("mkdir $dir: $!");
    }
    my $file = Plagger::Util::filename_for($args->{feed}, $self->conf->{filename} || "%i.html");
    my $path = File::Spec->catfile($dir, $file);
    my $io = IO::File->new("> $path");

    # output feeds
    my $feed = $args->{feed};
    my $encoding = $self->conf->{encoding} || 'utf-8';
    my $body = $self->templatize('html.tt', { feed => $feed, encoding => $encoding });

    $io->printf("%s\n", $body);

    # log
    $context->log(
        info => sprintf(
            "Write to %s: %d entries",
            $path,
            $args->{feed}->count
        )
    );
}

1;
__END__

=head1 NAME

Plagger::Plugin::Publish::HTML - Output to HTML

=head1 SYNOPSIS

  - module: Publish::HTML
    config:
      dir: /var/www/hoge
      encoding: euc-jp
      filename: my_%t.html

=head1 DESCRIPTION

This plugin creates HTML.

Template is loaded from assets/plugins/Publish-HTML/html.tt .

=head1 CONFIG

=head2 dir

Directory to save html files in.

=head2 filename

Filename to be used to create html files. It defaults to C<%i.html>. It
supports the following format like printf():

=over 4

=item * %u url

=item * %l link

=item * %t title

=item * %i id

=back

=head1 AUTHOR

yhara

=head1 SEE ALSO

L<Plagger>

=cut

Publish::GmailとPublish::CVSの実装を大いに参考にしました(ていうかほとんど繋ぎ合わせただけ)。

assetsはこんな感じ。assets/plugins/Publish-HTML/html.tt に置いてください。

[% USE util = Plagger.Util -%]
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=[% encoding %]" />
</head>
<body>
<h1>feed "[% feed.title %]"</h1>
url: [% feed.link %] <br/>

[% FOREACH entry = feed.entries -%]
<div style="border:1px dotted black; margin:0.5em; padding:0.5em">
<div>
<h2>entry "[% entry.title %]"</h2>
[% IF entry.icon %]
<a href="[% entry.permalink | html %]">
        <img [% util.dumbnail(entry.icon, width=150, height=60) %] style="border:0" align="right"
        src="[% entry.icon.url | html %]" alt="[% (entry.icon.title || entry.title) | html %]" />
</a>
[% ELSIF feed.image %]
<a href="[% feed.link | html %]">
        <img [% util.dumbnail(feed.image, width=150, height=60) %] style="border:0" align="right"
        src="[% feed.image.url | html %]" alt="[% feed.title | html %]" />
</a>
[% END -%]

[% IF entry.author %]by [% entry.author | html %][% END %][% IF entry.tags.size %] on [% entry.tags.join(',') %][% END %]</div>
[% IF entry.body -%]
[% IF entry.body.match('(?i)^<p[ >]') %][% entry.body %][% ELSE %]<div style="padding: 1em 0">[% entry.body %]</div>[% END %]
[% ELSE %]<br />[% END %]
<div style="font-size:0.8em">
        Posted on [% entry.date ? entry.date.format('Mail') : "?"%] |
        <a href="[% entry.permalink | html %]">permalink</a> [% FOREACH widget = entry.widgets %] |
        [% widget.html(entry) %][% END %]
        <br clear="all" />
</div>
</div>
<!--[% UNLESS loop.last %]<hr />[% END %]-->
[%- END %]
</body>
</html>

[ruby] Hpricotの:nth-childの実装が間違ってる件

1 originにしようとして -1 originになってるっぽいw

パッチ:

*** elements.rb 2007-10-08 17:52:23.000000000 +0900
--- elements.rb.new     2007-11-02 02:41:32.000000000 +0900
***************
*** 422,428 ****
        case arg
        when 'even'; (parent.containers.index(self) + 1) % 2 == 0
        when 'odd';  (parent.containers.index(self) + 1) % 2 == 1
!       else         self == (parent.containers[arg.to_i + 1])
        end
      end

--- 422,428 ----
        case arg
        when 'even'; (parent.containers.index(self) + 1) % 2 == 0
        when 'odd';  (parent.containers.index(self) + 1) % 2 == 1
!       else         self == (parent.containers[arg.to_i - 1])
        end
      end

[hiki] パッチを <<< で貼り付けられない

こっちはパッチなし(ごめんなさい)

[ruby] 変数に代入した値を簡単に確認する方法

例えば

n = calc(a,b,c)

というコードでcalcの返り値を見たいときは、

p n = calc(a,b,c)

とすると簡単に確認できる。

[misc] 知識と技術と発想

http://d.hatena.ne.jp/higepon/20071101/1193915666

知識と技術はともかく、発想ってどうやって鍛えるんだろう。

[vim] vimの楽しみ

当時はこのキーを押すと画面に文字が出るってのが楽しくてしかたがなかったので、どんなしょうもないプログラムを入力するのも楽しくてしかたがなかった

[2007-10-31 - ABAの日誌より引用]

vim使ってると「いくつかキーを押すだけで複雑な編集処理ができる」ってのが楽しくてしかたがないので、 どんなしょうもないプログラムを入力するのも楽しくてしかたがない (というとさすがに過言かw)。


2007-11-03

[event][javascript] Kanasan.JS (別名:prototype.jsのソースにツッコミを入れるオフ) に参加してきました

ちょっとしたイベントのはずが20人オーバーの中規模イベントになってしまった(笑) Kanasan.JS。 結局、途中から初心者/上級者の2グループに分かれてコードリーディングを進めることになりました (※初心者グループの方が内容のレベルが高かったという噂もあるけど…!?)。

感想ですが、prototype.jsのソース読みがこんなに面白いとは思わなかった! 他の言語ではありえないJavascriptならではの実装があったりして、とても良い企画だったと思います。 今回はまだ400行(全体の11%)しか進んでいないので、次回以降も続きができるといいなぁ。

とりあえず、大量の印刷物の用意など、いろいろな作業をしてくださった主催kanasanに感謝を。 イベントを行うきっかけを作った@ujihisaもGJですw

というわけで、コードリーディング中に出た話題についてまとめておきますね。

l.31

 K: function(x) { return x }

KってなんのKなんでしょうね…? (Kコンビネータでは?という話も出たけど、どう見てもKコンビネータではなくIコンビネータだし)

ちなみに使用例としては、allやanyのデフォルト引数として使用されているようです(今調べた)。

コンビネータというのは自由変数を含まない(=引数を使うだけ)の関数のことです。 コンビネータにもっと親しみたい方は、Unlambdaという言語を学んでみると良いと思いますw

l.102

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}

「イベントクラスにコールバック関数を登録する」という処理は javascriptでよくありますが、その際にthisを固定するための 関数のようです(bindを使わないと、thisが元のオブジェクトではなくイベントクラスの方を指してしまう)。

これは実際に使ってるところを見ないとよくわからんなー。

l.112

bindAsEventListener=bindのイベントリスナに特化したバージョン。 bindと同じくthisを固定するほか、実行されるときに第1引数が イベントオブジェクトになります。

   return __method.apply(object, [event || window.event].concat(args));

イベントオブジェクトの取り方がIEとFireFoxで違うので、 (前者はwindow.event, 後者は関数の引数) その違いをラップしてくれるようです。

l.126

   $R(0, this, true).each(iterator);

$RはRangeオブジェクトを作る関数。RangeはRubyistにはおなじみのクラスですね。

l.149

 var Try = {
   these: function() {

Try.these(f1, f2, f3); とやると、関数f1, f2, f3を順に実行して、 例外を返さなかったものの実行結果が返って来ます。 prototype.jsの中では、Ajaxのブラウザごとの違いを吸収するために 使われているようです。

 //l.932
 var Ajax = {
   getTransport: function() {
     return Try.these(
       function() {return new XMLHttpRequest()},
       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
       function() {return new ActiveXObject('Microsoft.XMLHTTP')}
     ) || false;
   },

これを綺麗に書きたいがためにTry.theseは用意されたのではないか?という説。

l.167

 var PeriodicalExecuter = Class.create();

指定した処理を一定時間ごとに実行してくれるクラス。 setIntervalを自分で使っても良いのですが、タイマを止めるのが 面倒なので用意されているようです。(タイマIDがラップされている)

あと、currentlyExecutingというフラグを使って重複起動を防いでくれるとか。 1秒ごとに起動する関数の実行に2秒かかってたら、2回目は起動しない、ということですね (ってそもそも、一つの関数で何秒もかかるとブラウザが固まって良くないのですが…)。

その他出た話題:

  • Q.setIntervalに指定する時間ってどれくらいにすればいい?
  • A.出来る限り長くする (負荷軽減のため)
  • A.「イベントが起こる間隔の半分くらい」

l.213

Stringクラスのgsubメソッド。javascriptにはもともと replaceというメソッドがあるのですが、 Rubyみたいに #{} を使いたいがために gsubが定義されているようです。

使用例(l.343):

   return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();

javascriptでは文字列がimmutable (内容を書き換えることができない)なので、文字列を切って貼って結果を作っています。

(余談ですが、この行の最初のgsubだけ「,」のあとにスペースが入ってることにツッコミが入ってました。)

l.239

Stringクラスのscanメソッド。Ruby由来の命名のはずなのに、 RubyのString#scanと返り値が違うのですが…。

l.244

 truncate: function(length, truncation) {
   length = length || 30;
   truncation = truncation === undefined ? '...' : truncation;
   return this.length > length ?
     this.slice(0, length - truncation.length) + truncation : this;
 },

文字列を指定した長さに切り詰めるメソッド。 lengthのデフォルトが30ってのがなんとも凄いです(笑)。 実用性重視ですね。

次の行では、truncation引数を省略したら '...' という文字列を使うようになっています(※javascriptでは引数の個数が違ってもエラーにならない)。=== は == の厳密なバージョンで、例えば

null == undefined  #=> true
null === undefined #=> false

のようになります。 他にも null == 0 がtrueになったり (←嘘でしたごめんなさい・11/6追記)、javascriptの == はいろいろと (他言語経験者には)予想しがたい挙動をするので要注意です。

ここで == と === の違いを一言で説明できれば良いのですが、 仕様書を見て諦めました(^^; ===は型もチェックする (==は暗黙の型変換を許す) という説明で、合ってるかな。

ちなみに、 null + 1 が 1になるとか、true + 1 が2になるとか、 false - 1が -1になるなんて機能もあります (Perl使いのはこべさん曰く:「まあ、良くある事ですよね」)。 Number(undefined) は NaNになります(さすがに)。

l.255

 stripTags: function() {
   return this.replace(/<\/?[^>]+>/gi, '');
 },

文字列からタグを取り除く関数、らしいのですが…、 これ本当に安全なんですかね?id:Hamachiya2さんあたりに検証して欲しいものです。

その後にstripScripts, extractScriptsというメソッドもありますが、 そこで使われている正規表現(l.27)も実に怪しいw (</script >と空白を入れるだけでマッチしなくなります)。

 ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',

まあ、「自分で書いたscriptタグを取り除く」ものだと 割り切って使えということでしょうか。

そこの正規表現を作っているところに「'img'」みたいな文字列がありますが、

   var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
   var matchOne = new RegExp(Prototype.ScriptFragment, 'im');

これは <img>タグのことではなく、正規表現のiオプションと mオプションとgオプションのことのようです(ignore case, multi line, global)。 なら 'gmi'とか 'gim'とか、まぎらわしくない順番に並べればいいのに…(笑)。

l.275

 escapeHTML: function() {
   var self = arguments.callee;
   self.text.data = this;
   return self.div.innerHTML;
 },

個人的に今回のハイライトだと思うのがこのescapeHTML関数です。 ご存じ「>」とか「&」を「&gt;」「&amp;」等にエスケープする関数なのですが、javascript以外ではあり得ない実装になっています。

まず、l.419以下で escapeHTML関数にdivとtextというプロパティを定義しています。関数すらオブジェクトとして扱うjavascriptならではですね。

 Object.extend(String.prototype.escapeHTML, {
   div:  document.createElement('div'),
   text: document.createTextNode('')
 });

そして次に、div.appendChild(text) で divノードにtextノードを子として追加しています。

 with (String.prototype.escapeHTML) div.appendChild(text);

これは普通に書くと

String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);

のようになるのですが、あまりに長いのでwith構文を使っているようです。

escapeHTML()が呼ばれた時は、まずarguments.calleeでescapeHTML関数自身を参照しています(arguments.callerが呼び出し元の関数、arguments.calleeが呼び出される関数を指す)。 そして、そのテキストノードのdataプロパティにthis(=escapeしたい文字列)を代入し、divノードのinnerHTMLを参照すると エスケープされた文字列が受け取れるという…。

escapeHTML()を日常的に使っている人も、まさかこんな実装になっているとは知らなかったんじゃないでしょうか(笑)。

divノードやテキストノードを関数のプロパティに保存するのは、 実行速度を上げるためのようです(createElementは結構重い)。 また、innerHTMLでエスケープされた文字列が取れるのはDOM 1 で規定されていてブラウザ互換らしい。

l.281

 unescapeHTML: function() {
   var div = document.createElement('div');
   div.innerHTML = this.stripTags();
   return div.childNodes[0] ? (div.childNodes.length > 1 ?
     $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
     div.childNodes[0].nodeValue) : '';
 },

続いてその逆のunescapeHTML関数です。 こいつはescapeされたタグを復活させるのですが、この3項演算子の 嵐、解読できるでしょうか?

if文に直すと以下のようになります。

   if(div.childNodes[0]) {
     if(div.childNodes.length > 1)
       return $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue })
     else
       return div.childNodes[0].nodeValue
   }
   else{
     return '';
   }

要するに、injectで子ノードの文字列表現を集めているのですが、 要素が0個とか1個の時を場合分けすることで、$Aやinjectによる オーバーヘッドを回避しているようです。

と分かればさっきのreturn文もなんとか読めるのですが、 コメントを入れるかインデントを工夫してほしいものです。(^^;

l.289

"http://.../?hoge=1&moge=2#footer" のようなクエリ文字列から、 {"hoge": 1, "moge": 2} のようなハッシュを作るメソッド。 最初の正規表現で「?」と「#」の間の文字列を取り出しています。

   var match = this.strip().match(/([^?#]*)(#.*)?$/);

括弧が2つありますが、欲しいデータは1つ目(match[1])だけなので、

   var match = this.strip().match(/([^?#]*)(?:#.*)?$/);

と書いても動作は変わりません。

この関数は引数を一つとり、セパレータを変えられるようになっています(デフォルトは'&')。'&' の他には ';' が代表的でしょうか。 ちなみに、逆の動作を行うメソッド toQueryString ではなぜか「'&'」に固定されています(^^;

decodeURIComponentはURI表現(%20とか)をデコードする標準関数です。 他にdecodeURIという関数もあって、挙動が違うようです。

URIにパラメータを埋め込む時にはencodeURIComponentの方を使えば良いようですね。(※encodeURIの方はいつ使うんだろう?)

toQueryParamsという関数名は(Rails使い以外にとって?)直感的じゃないね、という指摘がありましたが、今見たらparseQueryという別名が定義されていました(l.417)。

 String.prototype.parseQuery = String.prototype.toQueryParams;

l.313

文字列のsuccメソッド。Rubyのものと挙動が結構違うようですが、 まぁ文字列のsuccとか普通使わないし、どういう挙動が望ましいのか良くわからんです。

l.318

文字列のtimesメソッド。指定した回数だけ繰り返した文字列を作ります。…ここはinjectを使ってないのはなんでなんでしょうね?

l.342

文字列をCamelCaseにしたり、「_」区切りにしたり、「-」区切りにするメソッド(camelize, underscore, dasherize)が定義されています。 underscoreだけ「-ize」でないのは何故?と思ったら、underscoreという他動詞が存在する模様。

ちなみにunderscoreはCamelCaseをほぐすだけでなく、「::」を取り除いたりもします。いかにもRails用の機能ですね。

l.359

StringのtoJSONは、inspectと同じなんですね。

l.369

Ajaxの応答で内部的に使われているメソッド、isJSONおよびevalJSON。 evalに食わすまえに、「明らかにJSONでない」ものは弾いているのですが、その正規表現がこちら。

   return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);

真ん中あたりのアルファベットが気になりますが、

  • true
  • false
  • null
  • E (数値表現の 1.35E4 等で使う)

を全部合わせたもののようです。え、「s」が入ってないって? 角括弧の中なので、「r-u」が「rstu」と同じ意味になるのでした。

l.384

ある文字列がprefix/suffixになっているかを調べる関数、startsWithとendsWith。

Rubyにも欲しいなぁと言ってたら、1.9では導入されるようです。

でもends_with?, starts_with?ではないんだなぁ。 Python由来なのか。

絶望した!長すぎるレポートに絶望した!

JavaScript中級者(=俺)向けに解説を加えながら書くと、どうしても長くなってしまうなぁ。 ブクマ数は増えるが実際に読む人は減るという諸刃の剣メソッド。

[あとで読む][あとで読まない][あとで読んで]

本日のツッコミ(全2件) [ツッコミを入れる]

ema [お疲れ様でした。 Firebug 等で確認したところ、0 == null => false だと思います。]

yhara [> 0 == null => false だと思います やってしまったorz 修正しました。ご指摘に感謝!]


2007-11-06

[event][javascript] 今明かされる「Kanasan.JS」の本当の意味

「Kanasan.JS」とは「JavaScriptを愛する」という意味である。
主催者のハンドルネームと偶然ハミング距離が0というだけなのである。

そうだったのか!

そういえばイベント案内にも書いてありましたね。

「かなさん」は、漢字で書くと「愛さん」、意味は「愛しい」です。

[Kanasan.JSより引用]

冒頭の引用はujihisaさんの「Kanasan.JS まとめのまとめ」より。

[ruby][event] Rubyで解析してほしいWebページを募集します

今週金・土の関西オープンソース2007というイベントで、HpricotとかMechanizeについて 10分ちょっとのデモをすることになりました。

というわけで、デモで解析するWebページを募集してみたいと思います。

  • Webページに関する、自動化したい処理 (日記の投稿とか、手作業でやるのがめんどいなぁ、コマンドライン一発でできたらいいなぁ、という事項)
  • 情報を抜き出したいWebページ (○○のサイトの表をcsvに変換するとか)
  • HTMLを一括修正するような処理 (絶対リンクを相対リンクに修正するとか)

を思いついた方は、コメント欄とかトラックバックとかリンク張るとかメールとかでお知らせください。

10分しかないので必ずデモで取り上げるぜ!という保障はできないのですが、金曜までに応募してくださったものは 責任持ってチャレンジしますので、よろしくお願いします。


2007-11-08

[ruby] Ruby スーファミ作成記 (1) - 星一の日記

カードリッジに Ruby スクリプトをいれて、それをスーファミで起動できたら素敵じゃないですか。

[Ruby スーファミ作成記 (1) - 星一の日記より引用]

素敵すぎる!


2007-11-09

[prog] Re:いろんな言語で簡単なプログラムを書いてみるテスト(1)

こういう練習って面白いですよね。1人doukaku.orgというか。

Rubyのcatメソッドは、File#openを使うと(ファイルの閉じ忘れを防げるので)良いかもしれません。

def cat(name)
  File.open(name, "r"){|file|
    while(c=file.getc)
      putc c
    end
  }
end

ちなみに全体を読み出すだけなら File.read を使うのが便利です。

def cat(name)
  print File.read("name")
end

さらに題意を考えると、実はARGFを使うのが一番短くなりますw

print ARGF.read

Scheme版は letrec の代わりに named let(名前付きのlet)を使うとより簡潔に書けます。 また、リストの全ての要素に対して繰り返すような処理は for-each を使うのが簡単です。 (あとcondをifとbeginで置き換えてみましたが、これは好き好きかなぁ。)

(define (cat input)
  (let loop ((c (read-char input)))
    (if (eof-object? c)
      0
      (begin
        (write-char c)
        (loop (read-char input))))))

(define (main args)
  (for-each (lambda (arg)
              (call-with-input-file arg cat))
            (cdr args))
  0)

ちなみに、処理系をGaucheに限れば、file->stringを使うのが最も簡単です。

(use file.util)
(define (cat name)
  (display (file->string name)))

(define (main args)
  (for-each cat (cdr args))
  0)

ご参考までに。

本日のツッコミ(全2件) [ツッコミを入れる]

komuro(hogelog) [> 1人doukaku.org これはやりながら思ってました。doukaku.orgを探ったら題意すら同じのありそう..]

yhara [お役に立てたようで幸いです。 Gaucheは、実は便利なライブラリが山ほど添付されてるので、暇な時にでも眺めてみると..]


2007-11-11

[ruby][event] 関西オープンソース2007でMechanizeとHpricotの発表してきました

関西オープンソース2007のユーザ企画のRuby関西枠で、15分ほどの 短い発表をさせていただきました。

内容はWWW::MechanizeHpricotの簡単な紹介とデモです。

もうちょっと時間をとってスライドを練ればよかったなぁ。(と毎回言っている気もするが…)

[web] SlideShare

今まで使ったことがなかったのだけど、なんとなく登録してみた。

感想:

  • アップロードフォームがフルAjaxなのにプログレスバーが出てすごい(凝ってる)
  • PDFをアップロードするときは、フォントを埋め込まないと日本語が表示されないのに注意
  • 同じタイトルで再アップすると、URLに謎の数字が付加されて(´・ω・`) (まあ、仕方ないのだけど)
  • ファイルをダウンロードしようとするとamazonaws.comにリダイレクトされる(EC2?)
本日のツッコミ(全2件) [ツッコミを入れる]

かくたに [PDFのリンクのほうに数字が入ってないです。]

yhara [リンクを修正しました。ご指摘ありがとうございます。]


2007-11-12

[lisp] M式

最近知ったのだが、Lispの初期にはS式の他にM式という記法が考えられていたらしい。

「コンピュータプログラムの世界において M式 (meta-expressions) は、 Lisp言語の一部として、S式の人間が読むことの出来る形態となることを目的に考えられた。 M式はLispの初期の論文において理論的な言語として使われていたが、実際に実装されることはなかった。 」

S式は人間が読むもんじゃないのかよwとツッコミたくなりますね。

実行前にS式に変換するから「meta-expression」と名づけられた模様。

[1, 2, 3]                        (quote (1 2 3)) or '(1 2 3)
car[X]                           (car X)
car[append[[1,2,3], [4,5,6]]]    (car (append '(1 2 3) '(4 5 6)))
1 + [2 * 3]                      (plus 1 (times 2 3))

こっちの方がだいぶ「普通」なんだけど、LispプログラマはすぐにS式に慣れてしまったので M式は普及しなかったみたいです。 S式はプログラムとデータが同じ構造をしている(ので、マクロが使いやすい)けど、M式はそうじゃないですからね。

ところでこれ、じっと眺めていると関数型言語Rubyのコードに見えてきませんか? *1

*1 「callうざい」とよく言われるのだけど、実はProc#[]でも関数オブジェクトを起動できる

[ruby] M式がRubyに見えてしかたがないので

ということで、M式で書かれたLispプログラムを実行するインタプリタをRubyで書いてみた。

require 'mexp'

Mexp.eval{
  DEFINE[length,
    LAMBDA[[ls],
      IF[NULLP[ls],
        0,
        +[1, length[CDR[ls]]]
      ]
    ]
  ]
  DISPLAY[length[[1,2,3,4,5]]]
}

実行すると画面に「5」と出力します。

mexp.rbの実装はこちら:

制限:

  • 予約語は大文字で書きます。"if"を小文字で書くとパースエラーになってしまうので、泣く泣く。
  • 変数は小文字で書きます。こっちはconst_missingあたりでなんとかなるかも。
  • 整数とリストとCARとCDRと+しかありません。あとNULLP。
  • プログラムはいくつかのDEFINEと、ひとつの本体から成るものとします。
  • いまWikipedia見たら、+は中置記法なんだった。そりゃそうか。しまった。まあいいや。

[lisp] L式

http://en.wikipedia.org/wiki/M-expression によると、さらにsrfi-49のインデントベースLispが L式 (l-expression) と呼ばれるらしい。 (検索しても全然出てこないんだけど…)。

  define
   fac x
   if
    = x 0
    1
    * x
      fac
       - x 1

この見た目は斬新www

インデントというとpythonぽくなるのかなぁと思ったら、全然そんなことないですね。異形すぎる。

本日のツッコミ(全6件) [ツッコミを入れる]

Before...

yhara [確かに、角カッコで関数を呼び出すところとか似ていますね。 http://documents.wolfram.com/..]

otn [二年前のブログにコメントですが(はてぶで発見)、 私が初めて読んだLISPの本 http://www.amazon...]

はてな [lispのプログラムを処理するプログラムを記述するとき、 M式とS式の記述を両方同時に使うのが非常に解りやすいので ..]


2007-11-13

[ruby] ドライブレターの大文字/小文字が揃っていないとPathname#relative_path_fromが失敗する件

"c:\home" → "c:\tmp" はOK

"c:\home" → "C:\tmp" は不可

irb(main):002:0> home = Pathname("c:\home")
=> #<Pathname:c:home>
irb(main):003:0> temp = Pathname("C:\temp")
=> #<Pathname:C:        emp>
irb(main):004:0> tmp  = Pathname("c:\tmp")
=> #<Pathname:c:        mp>
irb(main):005:0> home.relative_path_from(tmp)
=> #<Pathname:../home>
irb(main):006:0> home.relative_path_from(temp)
ArgumentError: different prefix: "c:" and "C:\temp"
        from c:/prog/ruby/lib/ruby/1.8/pathname.rb:709:in `relative_path_from'
        from (irb):6

えー。そりゃないよ。

[vim] LustyExplorerをWindowsで使う方法

ファイル一覧やバッファ一覧を表示+タブ補完付きで選択できるプラグイン、LustyExplorerを入れてみましたよ。

手順:

  1. KaoriYa.netのVim7.1を入れる (7.0ではだめかも)
  2. http://www.vim.org/scripts/script.php?script_id=1890 からスクリプトをダウンロードして、$HOME\.vim\pluginとか、$HOME\vimfiles\pluginあたりに入れる
  3. 「\lf」でファイル一覧、「\lb」でバッファ一覧。キー入力でファイル候補選択、タブ補完あり。

別のディレクトリのファイルを開いているときは、「\lr」で「そっちのディレクトリの」ファイル一覧を表示してくれます。 便利。

良く使うコマンドだと思われるので、「,f」など適当なキーバインドで実行できるようにしておくと便利かも。

" Lusty Explorer/Juggler
nmap <unique> <silent> ,b :BufferExplorer<CR>
nmap <unique> <silent> ,f :FilesystemExplorer<CR>
nmap <unique> <silent> ,r :FilesystemExplorerFromHere<CR>
" nmap <unique> <silent> ,j :LustyJuggler<CR>

んで、Windows版だと:cdとかでディレクトリを移動したときに例のdifferent prefixなんちゃらが出る場合があるので、 やっつけパッチを書いてみました。

--- c:\tmp\lusty-explorer.vim	Tue Nov 13 17:39:19 2007
+++ lusty-explorer.vim	Tue Nov 13 17:11:21 2007
@@ -425,13 +425,21 @@
       '[LustyExplorer-Buffers]'
     end
 
+    def force_downcase_driveletter(path)
+      if path.to_s =~ /\A[A-Za-z]:/ 
+        path = Pathname(path.to_s.sub(/\A./){|c| c.downcase})
+      else
+        path
+      end
+    end
+
     def buffer_match_string
       pwd = Pathname.getwd
 
       name = if @curbuf_path.to_s.starts_with?("scp://")
                @curbuf_path.to_s
              else
-               @curbuf_path.relative_path_from(pwd).to_s
+               force_downcase_driveletter(@curbuf_path).relative_path_from(force_downcase_driveletter(pwd)).to_s
              end
 
       Displayer.vim_match_string(name, @prompt.insensitive?)
@@ -461,7 +469,7 @@
                  name
                else
                  path = Pathname.new VIM::Buffer[i].name_p
-                 path.relative_path_from(pwd).to_s
+                 force_downcase_driveletter(path).relative_path_from(force_downcase_driveletter(pwd)).to_s
                end
 
         @buffers[name] = VIM::Buffer[i].number
@@ -1074,6 +1082,7 @@
     @sidescroll = eva "&sidescroll"
     @sidescrolloff = eva "&sidescrolloff"
 
+    @reg  = vim_single_quote_escape(eva('@"'))
     @reg0 = vim_single_quote_escape(eva("@0"))
     @reg1 = vim_single_quote_escape(eva("@1"))
     @reg2 = vim_single_quote_escape(eva("@2"))
@@ -1117,6 +1126,7 @@
     exe "set sidescroll=#{@sidescroll}"
     exe "set sidescrolloff=#{@sidescrolloff}"
 
+    exe "let @\" = '#{@reg}'"
     exe "let @0 = '#{@reg0}'"
     exe "let @1 = '#{@reg1}'"
     exe "let @2 = '#{@reg2}'"

後半はcho45さんのパッチが混ざってます。 ごめんなさい。でもこっちも当てといた方がいいよ多分。


2007-11-15

[ruby][book] Ruby on Rails入門 優しいRailsの育て方

図書館で見つけたのでつい借りてしまった。

正誤表にない 間違いを見つけたので書いておく。

p.67

  • × プレースフォルダも利用可能です
  • ○ プレースホルダも利用可能です

(「placeholder」はプレースフォルダとは読みませんよね。)

p.71

  • × :updated_onまたは、:updated_on
  • ○ :updated_atまたは、:updated_at

2007-11-16

[ruby] Pattern Matching in Ruby (via jijixi's diary)

フィボナッチ:

def fib(n)
  match n do
    with(0) {0}
    with(1) {1}
    otherwise {fib(n-1) + fib(n-2)}
  end
end

木構造のダンプ:

def disp(tree)
  match tree do
    with(:num & Fixnum) {num}
    with([:left, :op & String, :right]){"(#{disp(left)} #{op} #{disp(right)})"}
    otherwise {"Invalid tree structure"}
  end
end

Haskell関西方面でウケが良さそう。

[ruby] ランダムに変わるスーパークラス?

class A < (rand % 2 == 0 ? String : Array)

end

[soutaro#nikki()より引用]

ええっこんなことできんの!?と思ったが:

yhara@meteor:~/tmp % ruby randklass.rb
[A, Array, Enumerable, Object, Kernel]
yhara@meteor:~/tmp % ruby randklass.rb
[A, Array, Enumerable, Object, Kernel]
yhara@meteor:~/tmp % ruby randklass.rb
[A, Array, Enumerable, Object, Kernel]

Arrayしか出てこないな。

11/18追記:と思ったらコメント欄で「randの使い方が間違ってる」という指摘が。

yhara@meteor:~/tmp % irb
>> rand
=> 0.775069450639572
>> rand
=> 0.79447336084156
>> rand
=> 0.396552445835088
>> rand % 2
=> 0.236850289302209
>> rand % 2
=> 0.879164452414756

そうか、rand()は0〜1までの数を返すんだっけ…。「0か1」が欲しいならrand(2)としなければならないと。

class A < (rand(2) == 0 ? String : Array)
  p self.ancestors
end

これでおk?

yhara@meteor:~/tmp % ruby randklass.rb
[A, String, Enumerable, Comparable, Object, Kernel]
yhara@meteor:~/tmp % ruby randklass.rb
[A, Array, Enumerable, Object, Kernel]
yhara@meteor:~/tmp % ruby randklass.rb
[A, String, Enumerable, Comparable, Object, Kernel]
yhara@meteor:~/tmp % ruby randklass.rb
[A, String, Enumerable, Comparable, Object, Kernel]
yhara@meteor:~/tmp % ruby randklass.rb
[A, Array, Enumerable, Object, Kernel]

なんという動的言語。

ご指摘ありがとうございました>konnさん、okkezさん

本日のツッコミ(全2件) [ツッコミを入れる]

konn [rand % 2と成っているからではないかと。 正しくはrand(2)ではないでしょうか。]

okkez [まえに氏久くんが似たような事をやってた記憶が。。。 (rand(10) % 2 == 0 ? String : A..]


2007-11-17

[GTD] GTDの秘密は脳トレにあった?

何気なく読んだ本にこういうのは前頭葉が傷んでるって書いてあった

[ダメ休日のパターン - 西尾泰和のはてなダイアリーより引用]

「部屋の片付けなどのめんどくさいことを毎日少しずつやると、前頭葉の指令を出す力が鍛えられる。」

[家事は脳トレ - 西尾泰和のはてなダイアリーより引用]

「めんどくさいことを継続的に少しずつ」ってGTDそのものじゃね?


2007-11-18

[softs] デフラグした

で紹介されているAusLogics Disk Defragを使ってみた。

今のところ特に問題なし。HDがガリガリ言うのは相変わらずだけど、ガリガリ音が多少静かになったような気はする。 「HDが遅い」とか言う前に、たまにはデフラグしてやらんといかんね。


2007-11-19

[event] LiveCoding#5に参加します

12/1に大阪で行われるLiveCodingというイベントに参加させていただくことになりました。 今回はLiveCoder側です。何作ろうかなぁ。

定員が少ないらしいので、申し込みはお早めにどうぞ。

[javascript][scheme] BiwaSchemeを公開しました

Javascriptで書かれたScheme処理系「BiwaScheme」を公開しました。

Kent Dyvbigの「Three implementation models for scheme」 の4章を参考に作られています。 スタックベース・中間言語方式で、call/cc、set!が実装されています。マクロはまだありません。 構文もまだ基本的なものしか用意されていません。

コア部分はブラウザに依存しないので、SpiderMonkey等のコマンドラインの処理系でも動作します。 IE6.0, Firefox2.0, SpiderMonkey1.6.1, CScript(WSH5.6)で動作を確認しています。

デモ

                <script src="biwascheme.js">
                (display "Hello, World!")
                </script>

他の処理系について

  • jsScheme
    • Javascriptで書かれたものとしては、現時点で一番完成度の高い処理系です。R5RSにほぼ準拠しています。call/cc、マクロ、JITコンパイル等の機能があります。パーザ部分をそのまま流用させていただいてます。
  • WebScheme
    • 卒論のときにいじっていたもので、上のjsSchemeにWeb関連の機能を加えたものです。実行速度があまり速くないのと、JITと継続の相性が悪いことが、今回の再実装の動機になりました。
  • id:gnarlさんのやつ
    • 言語開発合宿の時に(隣の席で)作られていたものです。同じドキュメントを参考に作られていますが、4章ではなく3章がベースになっているようです。
  • 地獄Scheme
    • 京都の若きLisper、zickさんによる処理系です。ソースがなんとなくCっぽいです。BiwaSchemeのソースはSchemeっぽいかもしれません(元がScheme in Schemeなので)。CodeZineの記事のアップグレード版のようです。
  • id:higeponさんのやつ(C++)
    • こっちは4章ベースなので、実はBiwaSchemeと一番似た構造になっているはずです。が、向こうはどんどん実装が進むので、こっちも追いつかねばという気にさせられて開発が進みました(笑)。ありがとうございます。

今後の予定

APIの拡張
とりあえずWebScheme相当の機能は載せる予定。
構文の拡張
condとかまだ実装してない構文を。
関数の拡張
jsSchemeがR5RSベースなので、こっちはR6RS準拠を目指すことにします(笑)。
パーザを自前のものに
#0等の記法に対応したい。

[biwascheme] メモ

higepon版(←そろそろなんか名前をつけてほしいな)との違いとか、もう少し詳しく。

  • コンパイラ部分の実装言語
    • BiwaSchemeはコンパイラ部分もJavascriptで実装している(というか、Scheme版を作ろうとはしたんだけどうまく動かなかったんで、選択肢が無く…。ほとんどコピペするだけなのに…。)
  • ライブラリの実装言語
    • BiwaSchemeは、ライブラリ関数もなるべくJavascriptで書く予定です。高速化のために。
    • …しかし本当に速くなるんだろな。まぁ関数によるか。
    • mapとか、高階関数をどうするか考え中。
  • ライブラリのarity
    • jsSchemeはエラーチェックがほとんど無かったので、その辺をもうちょっとなんとかしたい。
    • とりあえずライブラリ関数は「引数の最小個数」「引数の最大個数(無限の場合はnull)」をデータとして持つようにした。
    • というのを実装するために、apply命令の引数を一つ増やした(渡された引数の数)。
  • 関数のarity
  • 正規表現
    • どうしようかなぁ。
  • 命令コードの定数化
    • これはすごいwww
    • やりたいけど、デバッグとか大変にならないかなぁ。デバッグ時だけ命令コードを文字列で出力すればいいのか。
    • どれくらい高速化できるかわからんので、もうちょっと動かしたいコードの規模が大きくなってからでいいかな。
  • read
    • Gaucheのが使いまわせるのは正直うらやましいw
  • letとかbeginとか(1, 2)
    • 今のところ、リスト操作のコードをJavascriptで書いて対処している。
    • jsSchemeはこのへんを「Schemeで書いた処理をJITコンパイルしたコード」で行ってて、結構時間を食ってたような。
    • まぁリスト操作とかSchemeの方が100倍楽なのは明らかなんだが。変換にかかるコード量がそんなに多くないので今のところおk。
  • 数値演算を組み込み命令に
    • どうしようかなぁパート2。
    • これももっと大きくなってから検討するか。
  • global
    • jsSchemeにはTopEnvとCoreEnvという2種類のグローバル環境があって、TopEnvはユーザ用、CoreEnvは標準ライブラリ用と分かれていた。
    • ので、BiwaSchemeも最初はrefer-topenvとrefer-coreenvとか作ってたんだけど、よく考えると分ける必要が分からないのでrefer-globalに統一した。
  • 命令の引数順序
    • frameの第二引数と第三引数を入れ替えたりした。こうすると、ダンプしたときに frame→関数→残りの処理 という順序になる(実行順序と見た目が一致する)ので分かりやすい。
  • define
    • (define a (lambda () b)) (define b 1) (display (a)) みたいなコードってvalidなんですよね。嫌だなぁ…(笑)。
    • あとでやる。
  • jsSchemeとの違い(気をつけているところ)
    • 速度を気にする。
    • 名前空間を汚さない。
    • コンソールでも動く。
    • デバッガ(←というほどのものでもない)を頑張る。
      • HTML+Javascriptは画面を作るのがとても簡単で良いです。やっぱりログに色付けしたりレイアウトできるのは大きいなぁ。
      • デバッガが楽しいと開発も楽しい。
  • ユニットテスト
    • 「これが通ったらR6RS認定!」みたいなテストケースがどっかに落ちてないものか。
  • 行程
    • 9月前半:印刷したまま放置してあった3imp.pdfを(超適当に)流し読みした。
    • 9月後半:KMCのコーディング合宿があったので、3章のヒープベースのを実装してみる。というのが言語開発合宿でid:gnarlさんと思いっきり被って焦ったw。
    • 10月前半:ヒープベースのが一応動く。が、下手にオリジナリティを加えたせいで3.5の改良ができないことに気付く。なんとなく放置。
    • 10月後半:酔った勢いで(LiveEatingの日ですね)、スタックベースも動かしたくなる。4章をひたすらjsにコンバートする(酔っているのでコードの意味は理解していない)。
      • 変数の型がないので四苦八苦しつつデバッグする。
      • 前回の経験を生かし、できるだけ原点に忠実に移植。
      • 構造は違えど、作業としては2回目なので楽。2回目いいよ2回目。
    • 11月前半:「4章を真面目に読まないとどうしようもない」という結論に達する。そりゃそうだろjk。
      • setと書いてあるのに追加順序に意味があることに気付き愕然とする。孔明の罠か。
      • collect_freeはSetを引数に取るべきなのに、Pairを渡していたことが(ようやく)発覚。リストの長さを求めるコードが動いた。
      • call/ccとset!を実装した。第4章完。
    • 11月後半:構文や関数を追加したりなんだり←いまここ

[biwascheme] あーあと

http://b.hatena.ne.jp/todesking/%e8%a8%80%e8%aa%9e%e3%81%ae%e5%ae%9f%e8%a3%85/ が参考になるという噂。

http://noncopyable.hp.infoseek.co.jp/soft01/make_cps.html とか凄いな。すごすぎるな(笑)。C++怖い。

// ありがちな関数内再帰関数(こういうのが C++ での CPS 変数となるらしい)
int s_expr_tail(vector<var_t>& _){
  struct __  : boost::noncopyable {
    int ___; __() : ___(0) {} typedef vector<var_t> ____;
    int _____(____& ______) {
      ______.push_back(regist_num(___++));    // 式の終点にレジスタを置く
      ____::iterator _______(______.begin()),________(______.end());
      for(;_______!=________;++_______){
        if(var_t::type_num<____>() == _______->which() ){
          (void)_____(get<____>(*_______));
        }
      }
      return ___;
    }
  } _________;
  return _________._____(_); // 下バー使いまくり
}

2007-11-20

[biwascheme] sleepを実装した

BiwaSchemeに、指定したミリ秒だけ実行を停止する関数「sleep」を実装した。アニメーション等には必須の機能である。

sleepが簡単じゃない

普通の言語であればsleepなんて簡単なわけだが、ことJavascriptにおいては、sleepの実装は自明ではない。 Javascriptに「指定した時間だけ待つ」という命令はなく、setTimeoutで一定時間後に指定した関数を実行することしかできないからだ。 (無限ループでむりやり待つことは可能だが、その間ブラウザが固まってしまうのでやってはいけない^^;)

というわけで、sleep前にインタプリタの状態を保存し、setTimeoutで待ったあとにインタプリタを再開するようにした。

sleep関数の定義はこんな感じになった。pauseという変数がインタプリタの途中の状態(スタック及び評価中の値)を保持している。

  define_libfunc("sleep", 1, 1, function(ar){
    var msec = ar[0];
    return new WebScheme.Pause(function(pause){
      setTimeout(function(){ pause.resume(nil) }, msec);
    });
  });

計算結果が行方不明

さて、ここで一つ問題が発生する。

(begin (display "a") (sleep 1000) 1)

というような式を評価したとき、インタプリタは1を結果として返すべきである。しかし、今までのように

var result = biwascheme.evaluate(exp);

としただけでは、1ではなくPauseのインスタンスが返ってしまう。最終的な計算結果はsetTimeoutの後にしか分からないので、 evaluate関数からは知ることができないのである。

というわけで、ちょっと呼び出し方法を変えて、以下のように書くことにした。

biwascheme.evaluate(exp, function(result){ puts(result); });

式の実行前に「計算が終わった後の処理」をインタプリタに登録し、pauseオブジェクトを作るときにはこれも保存するようにする。 で、計算が終わったときにこの処理を呼び出してやる。

これで、上のような式でもちゃんと1が表示されるようになった。めでたしめでたし。

beginがないとき

では、全体を囲むbeginがなく、

(display "a") (sleep 1000) 1

と書かれていた場合はどうするべきだろうか?

今の実装では、REPLでこれを実行すると

a
1
undefined

という実行結果になる。

実際、Gaucheでも同じような結果になるので、Schemeの意味論的にはこれでも良いようだ。

でも、実用を考えると、beginがあるときと同じように 「a→(1秒後)→1」と表示されてほしい気もする。 これを実装するなら、全体を強制的にbeginで囲んでしまうのが簡単だけど…。それによって困ることってあるかな。


2007-11-22

[biwascheme] 10歳から始める家庭のSchemeプログラミング

BiwaSchemeが Wiiブラウザ上で動いたそうです。

biwascheme on wii

動かし方は簡単、Wiiでブラウザを開いて http://mono.kmc.gr.jp/~yhara/biwascheme/ にアクセスするだけです。

キーボードがないとS式の入力が非常に大変なので(笑)、PCからUSBキーボードを引っこ抜いて接続してあげてください。

入力ボックスが一度に2〜3行しか表示できないのでなかなか大変ですが、 小さい関数をこまめに定義していくボトムアップなプログラミングスタイルを身に付けるためにはむしろプラスに働くかも知れません(ほんまかいな)。

画像の説明

ユニットテストも100% pass。Opera++

さらに、なんとなんと、iPod Touchでも動いたそうです。

画像の説明

こっちはスタックの使いすぎでユニットテストが失敗してますが、 そのうち処理系の再帰部分をループに置き換える予定なので、それがうまく行けば通るようになるかも知れません。

上のスクリーンショットは @masa_edw さんが撮ってくれました。ありがとうございました。

原寸:

[kmc] 学園祭でBiwaSchemeを展示しています

今日から始まった京都大学11月祭KMCの展示ブースにて、 上の BiwaScheme on Wii が展示されています。

今日は「factorialが定義できない」という苦情が何件か来たらしいのですが *1 さっきアップデートしたので明日はちゃんと動くはずです。

腕に覚えのある方は、末尾再帰バージョンも書いてみて、実行速度の差を比べてみると楽しいかも知れません :-)

*1 原因1:defineで再帰関数が定義できなかった 原因2:「=」が無かった(笑)


2007-11-24

[biwascheme] http-request

BiwaScheme でXHRが出来るようになったらそれを使ってフルScheme Wikiにするか

[そうそうBiwaScheme - ひげぽん OSとか作っちゃうかMona-より引用]

既に実装されている件。

http-requestという関数にパス名を渡すと、サーバ側のリソースにAjaxでアクセスします。

(display (http-request "README.txt"))

(テストはこちらでどうぞ)

でもって、上のスクリプトを見れば分かるように、アクセスは同期的に行われます。 この前のsleep関数の応用で、Ajax開始前にインタプリタの情報を保存しておき、 結果が取得できたらそれを引数としてインタプリタを再開してやると。

理論的に可能なのは分かってたんですが、実際に動いたときは感動しました。

あとはPOSTと、非同期なアクセスがあればいいのかな?

[scheme] #!/bin/mosh

http://d.hatena.ne.jp/higepon/20071122/1195700048

"moche"はあまりいい意味がないとのことだけど、どっちかというと モッシュ&ダイブ の方を思い出した。


2007-11-27

[prog] PAC(Presentation-Abstraction-Control)パターン

なんかMVCの進化系パターンとして、PACなるものがあるらしい。

MとVとCを持った小さいコンポーネントがたくさんあり、それぞれがCを介してやりとりする…みたいなイメージか。

2番目のサイトでは、使用例として「新しいGUIコンポーネントを開発するとき」が挙げられている。 ボタンやパネルなどいろいろなコンポーネントがあり、それぞれのCを通してイベントをやりとりすると。

[ruby] LiveConsole

実行中のRubyプログラムにTCP経由で接続して、デバックプリントしたりパッチ当てたりなんだり、というソフトウェア。

Lisperはよくそういうことをやる(らしい)けど。

まだ認証機能がないので、イントラネット以外で使わないように、とのこと。

[ruby] Rubyスクリプトであり、バッチファイルでもあるファイルの作り方

こういうのがテンプレらしい。

@echo off
ruby -x "%~f0" %*
goto :EOF

#!ruby
#---------------ruby script starts here
puts "hello, rb.bat"
#---------------ruby script ends here
__END__
:EOF

-xなんてオプションがあったんですねえ。

メッセージ中のスクリプトを取り出して実行します。スクリプトを読み込む時に、`#!'で始まり, 'ruby'という文字列を含む行までを読み飛ばします。スクリプトの終りはEOF(ファイルの終り), ^D(コントロールD), ^Z(コントロールZ)または予約語__END__で指定されます。

[Rubyリファレンスマニュアル - Rubyの起動より引用]

「%~f0」についてはDOS窓で call /? なんかを。

wget.rb.bat

試しに、超適当なwgetもどきを作ってみました。

@echo off
ruby -x "%~f0" %*
goto :EOF

#!ruby
#---------------ruby script starts here
require 'open-uri'

if ARGV.size == 0
  puts "usage: #{$0} url"
else
  url = ARGV[0]
  fname = File.basename(url)
  
  puts "downloading #{url} to #{fname}..."
  File.open(fname, "w") do |f|
    f.write open(url){|io| io.read}
  end
  puts "done."
end

#---------------ruby script ends here
__END__
:EOF

普通にやると

  1. wget.rbを書く
  2. それを呼び出すwget.batを書く
  3. それらをPATHが通ったディレクトリに置く

という手順になるんですが、これだと wget.rb.bat というファイルを作るだけで済むので楽ですね。

本日のツッコミ(全2件) [ツッコミを入れる]

garyo [始めましてgaryoです。 Windowsのバグのせいで標準入出力でリダイレクトパイプを使うRubyスクリプトはBA..]

yhara [らしいですねぇ。僕は単純に、rbの関連付けをしてなかっただけなんですが…^^;]


2007-11-28

[softs] cvs2svnを使ってみた

CVSを使ってた頃のプロジェクトに後輩がパッチを書いてくれたので、いい機会だと思ってcvs2svnを 試してみました。

使用方法は aptitude で cvs2svnをインストールして、

cvs2svn --fs-type=fsfs --encoding=EUC-JP -s /svn/yhara/irolog /cvs/yhara/repository/irclog

とやればSVNリポジトリの出来上がりです。非常に簡単ですね。

元のCVSリポジトリはコミットログに複数の文字コードが混在してるという非常にアレな代物だったんですが、 --encoding=EUC-JPを付けたらどっちもEUCにまとめて変換してくれたようです。cvs2svnえらい。 (python-japanese-codecsが必要かも知れません:参考)


2007-11-30

[ruby] Debianにrubygemsをインストールしてみた

Debianにrubygemsをインストールしてみました。

結論から言うと、aptitudeで簡単に入るんですけど、2点だけ気をつけないといけないことがあります。

(1) gem置き場を変更したいとき

デフォルトでは/var/lib/gems以下にgemが置かれるので、自分の$HOMEなど別の場所を gem置き場にしたいときは、

  $ cp -r /var/lib/gems/1.8/gems/sources-0.0.1 $GEM_HOME/gems/
  $ cp    /var/lib/gems/1.8/specifications/sources-0.0.1.gemspec $GEM_HOME/specifications/

のようにしてsourcesという最初から入っているgemをコピーしてやる必要があります。

これを忘れると

yhara@mono:~ % gem install Hpricot
/usr/lib/ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require': no such file to load -- sources (LoadError)
        from /usr/lib/ruby/1.8/rubygems/custom_require.rb:27:in `require'
        from /usr/lib/ruby/1.8/rubygems/remote_installer.rb:462:in `sources'
        from /usr/lib/ruby/1.8/rubygems/remote_installer.rb:472:in `source_index_hash'

のように、gem installができなくなります(というかなりました)。

(2) gemでインストールしたコマンドはパスが通らない

gemによっては、独自のコマンドラインユーティリティーが付属しているものがあります(Railsのrailsコマンドとかが 代表的でしょうか)。

これらはデフォルトでは /var/lib/gems/1.8/bin にインストールされるので、 ここに手動でパスを通してやる必要があります。 (gem置き場を変更しているときは $GEM_HOME/bin に)。

まとめ

…というようなことが /usr/share/doc/libgems-ruby1.8/README.Debian に書いてありました^^;;;;

aptitude関係ではまったときはまず /usr/share/doc/(パッケージ名)/README.Debian がないか探してみましょう、という話でした。

おまけ

debパッケージの中身を解凍したいときは、

dpkg-deb --extract /var/cache/apt/archives/(パッケージ名とバージョン).deb

とすればいいみたい。