2009年12月27日

WebアプリでSQLiteを使ってみる (Perl)

SQLiteは、"most widely deployed SQL database engine in the world" だそうである (SQLite のホームページより)。
しかも、設定不要でサーバーの設置の必要もなく、トランザクションにも対応している。
こんな重宝なものを使わずに放っておくのはもったいない。
そんなわけで、(今さらながら) SQLite を使ってみた。

今回使用した環境は、
・SQLite
・Perl (および、DBI, DBD:SQLite)
・OS は Fedora 10
たいていのレンタルサーバーであれば、(OSはさておき) こういう環境が用意されているのではないだろうか。

以下は、簡単なアドレス帳の Web アプリの作例。

(1) ドキュメント

SQLite のホームページでドキュメントが整備されている。
以下のレファレンスがあれば、だいたい事足りそうだ。
http://sqlite.org/lang.html
これを眺めてみると、意外なほど機能が充実していることが分かる。

(2) sqlite3 コマンドでデータベースファイルを作成

sqlite3 は、コマンドラインから SQLite データベースファイルを操作するためのコマンドである。

SQLite では、個々のデータベースファイルが MySQL や PostgreSQL の「データベース」に相当する。
たとえば、コマンドラインから
$ sqlite3 addrbook.db
を実行すると、addrbook.db というデータベースファイルが作られ、SQLite のプロンプトが表示される。
sqlite>
この SQLite プロンプトに SQL 文を入力すれば、指定したファイル (上記の場合は addrbook.db) にデータが書き込まれていく。

なお、
sqlite> .help
と入力すると SQLite コマンド一覧が表示されるので、だいたいの機能の見当がつく。
(.databases というコマンドがあり、データベース一覧を表示できる。ということは、1 ファイルで複数のデータベースを保持できるのだろうか? 今のところ、真相不明)

さて、必要なテーブルを sqlite3 で作っておこう。
sqlite> CREATE TABLE personal ( name, addr, tel, email );
sqlite> .quit
これで、データベースファイル addrbook.db の準備は完了である。

(3) Webアプリからの操作

では、Web アプリから Perl / DBD::SQLite 経由でデータベースファイルにアクセスしてみよう。

まずは、サンプルコード。
※SQLインジェクションなどの対策はまったくしていないのでご注意。
#!/usr/bin/perl
use strict;
use CGI qw(param);
use DBI;
# データベースへの接続
my $dbname = 'addrbook.db';
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbname", '', '') or do {
print_err('データベースに接続できない');
exit 0;
};
if (param()) {
my $name = param('name');
$name =~ s/'/''/g;
$name =~ s/\\/\\\\/g;
my $addr = param('addr');
$addr =~ s/'/''/g;
$addr =~ s/\\/\\\\/g;
my $tel = param('tel');
$tel =~ s/'/''/g;
$tel =~ s/\\/\\\\/g;
my $email = param('email');
$email =~ s/'/''/g;
$email =~ s/\\/\\\\/g;
my $sql = "INSERT INTO personal ( name, addr, tel, email ) "
. "VALUES ( '$name', '$addr', '$tel', '$email' )";
$dbh->do($sql) or do {
print_err("SQL実行失敗:<br>$sql", __LINE__);
exit 0;
};
}
my $sql = 'SELECT name, addr, tel, email FROM personal';
my $sth = $dbh->prepare($sql) or do {
print_err("SQL準備失敗 ($sql)", __LINE__);
exit 0;
};
my $rc = $sth->execute or do {
print_err("SQL実行失敗 ($sql)", __LINE__);
exit 0;
};
print <<EOS;
Content-Type: text/html;charset=utf-8
<html>
<head>
<title>アドレス帳</title>
</head>
<body>
<form action="addrbook.cgi">
名前: <input type="text" name="name"><br>
住所: <input type="text" name="addr"><br>
電話: <input type="text" name="tel"><br>
メール: <input type="text" name="email"><br>
<input type="submit" value="追加">
</form>
<table border="1">
<tr>
<th>名前</th>
<th>住所</th>
<th>電話</th>
<th>メール</th>
</tr>
EOS
while (my $ref_row = $sth->fetchrow_hashref) {
print " <tr>\n";
print " <td>", $ref_row->{'name'}, "</td>\n";
print " <td>", $ref_row->{'addr'}, "</td>\n";
print " <td>", $ref_row->{'tel'}, "</td>\n";
print " <td>", $ref_row->{'email'}, "</td>\n";
print " </tr>\n";
}
print <<EOS;
</table>
</body>
</html>
EOS
$dbh->disconnect;
sub print_err
{
my $msg = shift @_;
my $line = $#_ >= 0 ? shift : undef;
print <<EOS;
Content-Type: text/html;charset=utf-8
<html>
<head>
<title>エラー</title>
</head>
<body>
<h1>エラー</h1>
EOS
if ($line) {
print "l.$line<br/>";
}
print <<EOS;
<div>$msg</div>
</body>
</html>
EOS
}
1;
これを、UTF-8 エンコーディングで addrbook.cgi というファイル名でしかるべき場所に、addrbook.db とともに保存すればよい。
(addrbook.db は、Apache から読み書きができるようにパーミションを変更しておく必要がある)

addrbook.cgi をブラウザで開くと、上半分がアドレスの追加フォーム、下半分がアドレス一覧になるはずだ。

上のコードでは、DBI->connect でデータベースファイルに接続し、do で INSERT し、prepare & execute で SELECT を実行し、fetchrow_hashref で SELECT 結果を読み出している。
posted by K/I at 07:45 | 東京 ☀ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする

2009年12月05日

携帯アプリの作り方 -- iアプリ(DoJa)編

iアプリの開発環境は、NTT から提供されている (Windows版のみ)。
DoJaプロファイル向けの開発ツールダウンロード
http://www.nttdocomo.co.jp/service/imode/make/content/
iappli/tool/doja/index.html
今回は、現時点での最新版「iαppli Development Kit for DoJa-5.1」(「iアプリ開発キット」と呼ぶことにする) を使用した。

(1) Eclipse をインストール

Eclipse 上で開発するほうがなにかと便利なので、Eclipse をインストールしておくことにしよう。
iアプリ開発キットは Eclipse 3.0.* および Eclipse 3.1.* にしか対応していないので、そのバージョンの Eclipse を用意する必要がある。

旧バージョンの Eclipse は、eclipse.org のサイトからダウンロードできる。
http://archive.eclipse.org/eclipse/downloads/index.php
今回は、3.1.2 をダウンロードして、C:\ 直下に展開した。
ほかのバージョンの Eclipse と区別するために、C:\eclipse_3.1.2 とフォルダ名を変更しておく。

なお、ランゲージパックを探すのがめんどうだったので、とりあえず英語のまま。

(2) iアプリ開発キットのインストール

前述の NTT のダウンロードサイトから、iアプリ開発キットの 5.1 をダウンロードし、実行する。
インストーラが展開されるので、DISK1 フォルダ内の setup.exe を実行する。
途中、「セットアップタイプ」画面で[カスタム]を選択する。
次の「機能の選択」画面で、[Eclipse3.0/3.1 プラグイン]にチェックを入れて次に進む。
iアプリ開発キットのインストール
「Eclipse 3.0/3.1 インストールディレクトリの指定」画面では、(1) で展開したフォルダ名 (今回は C:\eclipse_3.1.2) を選ぶ。

あとは、指示に従ってインストールを進める。

※NTTのダウンロードサイトの「3Dグラフィックスのエミュレート」に、3Dグラフィックス操作用のツールについて書かれているが、今回は割愛。

なお、iアプリ開発キットのインストール先をデフォルト以外のフォルダにした場合には、Eclipse 起動後に設定の変更が必要のようだ。

Eclipse を起動し、[Window] メニューの [Preferences...] で「Preferences」画面を開き、左のツリービューから [DoJa-5.1 Environment] をクリックして、「iαppli DevelopmentKit for DoJa-5.1のインストール先」が正しいフォルダが指定されているか確認しておく。

(3) テストアプリの作成 (HelloWorld)

とりあえず、HelloWorld アプリを作ってみることにしよう。

[File] メニューから[New]->[Project...] を選び、「Select a wizard」画面で "Java" の中の "DoJa-5.1 プロジェクト" を選んで [Next] ボタンをクリック。
プロジェクト名を HelloWorld として作成する。

プロジェクトができあがったら、Package Explorer で src フォルダにパッケージ hello、その中にクラス World を作成することにしよう。
クラス World の Superclass は "com.nttdocomo.ui.IApplication" とする。
iアプリの作成 - クラスを定義

World.java ファイルに、たとえば以下のような変更を加える。
package hello;
import com.nttdocomo.ui.Display;
import com.nttdocomo.ui.IApplication;
import com.nttdocomo.ui.Label;
import com.nttdocomo.ui.Panel;
public class World extends IApplication {
public void start() {
Panel obj_panel = new Panel();
obj_panel.add(new Label("Hello World!"));
Display.setCurrent(obj_panel);
}
}
では実行してみよう。

[Run] ボタンをクリックすると、"Create, manage, and run configurations" 画面が表示される。
左のツリービューから [DoJa-5.1 アプリケーション] を選び、下の [New] ボタンをクリックする。
項目が追加されるので、[Name] 欄にたとえば "HelloWorld" と入力し、[Run] ボタンをクリックする。

iアプリ開発キットが起動して、"Hello World!" と表示されれば実行成功である。
iアプリの作成 - 起動

※もし、下のようなエラー画面が表示されたら、Path 環境変数に JDK の bin フォルダを追加し、Eclipse を再起動すると直るかもしれない。
iアプリの作成 - jar ファイルが作成できない

(4) iアプリの公開

作成したiアプリは、bin フォルダの中に作成される。

フォルダを開いてみると、HelloWorld.jar のほかに、HelloWorld.jam と Download.html が作られている。
HelloWorld.jam はテキストファイルで、iアプリの情報が記載されているので、必要に応じて書き換える。
Download.html は、iアプリをダウンロードするための HTML ファイルなので、これも必要に応じて書き換える。

この 3 ファイルを Web サーバーにアップロードし (Download.html はファイル名を変更してもかまわない)、docomo 端末で Download.html を開く。iアプリのダウンロードリンクが表示されているので、クリックするとアプリがダウンロードされ、docomo 端末にインストールされる仕組みである。
posted by K/I at 18:46 | 東京 ☔ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする

2009年11月28日

とうとう直らなかった Windows XP のバグ

しばらく Windows XP 環境が無くて不自由していたが、最近 XP の Net PC を購入して使えるようになった。

さて、以前から気になっていたバグがまだ直っていないことに気づいたのでご報告。
それは、*.exe ファイルなどを、Windows の[スタート]メニューの[すべてのプログラム]にドロップして項目追加すると、正常に追加されないことがある、というバグだ。

以下の場合に生じるらしい

C:\Documents and Settings\All Users\スタート メニュー\プログラム に、たとえば「道具箱」というフォルダがあるとする。
ログインユーザー名が "michael" だとして、C:\Documents and Settings\michael\スタート メニュー\プログラム には「道具箱」フォルダは存在しないとする。
この時、あるアプリケーション appli.exe をドラッグして、[スタート]→[すべてのプログラム]→[道具箱]の中にドロップすると、なぜか消えてしまう。
どこに行ってしまったのかというと、C:\Documents and Settings\michael\スタート メニュー\道具箱 というフォルダが作られていて (プログラム フォルダが抜けている)、その中に "appli.exe へのショートカット" が置かれている。

C:\Documents and Settings\michael\スタート メニュー\プログラム\道具箱 が存在する場合にはこの現象は起きない。
また、すでにスタートメニューに登録されている項目を移動 (またはコピー) した場合には正常な位置に「道具箱」フォルダが作られて項目が追加される。

Microsoft はこれに気づいていないのか、それとも気づいていてもどうでもいいと思って直していないのかわからないが、なんだかなー。
タグ:Windows
posted by K/I at 12:36 | 東京 ☀ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする

2009年11月21日

Go を試してみた

Google の Go を試してみた。
http://golang.org/
Go はコンパイル時間がきわめて短く、GC (ガベージコレクタ) 機能を持つ、といった特長があるそうだ。

生粋のプログラマというわけではないので開発言語としてどうなのか評価することはできないが、インストールメモとして書いておきたい。


今回試した環境は Fedora Core 10。
Mac OS X (10.5 または 10.6) でも使えるようなので、いずれやってみたい。

インストール手順は以下のとおり。

(1) Mercurial というソース管理ツールをインストール
# yum install mercurial

(2) 以下の環境変数を設定。
$GOROOT
Go ソースのダウンロード先
$GOOS
OS名。今回は linux。Mac OS X なら darwin
$GOARCH
アーキテクチャ。amd64、386、arm のいずれか
$GOBIN
バイナリのインストール先。デフォルトは $HOME/bin
(3) Mercurial を使ってソースをチェックアウト
$ hg clone -r release https://go.googlecode.com/hg/ $GOROOT

(4) コンパイル&インストール
# cd $GOROOT/src
# ./all.bash

※インストールには、bison、gcc、ed、make などが必要。
最後に、
--- cd ../test
N known bugs; 0 unexpected bugs

と表示されれば正常終了 (N のところにはバージョン番号が表示される)。

インストール中に $GOBIN/quietgcc というシェルスクリプトが作られ、それを使ってコンパイルされたりしていて興味深い。

なお、Mercurial は Linux カーネルの開発の中で産み出された分散型バージョン管理システムだそうである (実際に Linux カーネル開発に採用されたのは Linus 氏が開発した Git)。
Git は聞いたことがあったが、Mercurial というのもあったのか。
いろいろと勉強になる。

※ @IT に機能比較の記事があった。
分散バージョン管理Git/Mercurial/Bazaar徹底比較
http://www.atmarkit.co.jp/fjava/rensai4/devtool03/devtool03_1.html

さて、インストールされるコンパイラ、リンカーのコマンド名はそれぞれ AgAl、中間ファイルは *.A、最終生成物は A.out だ。
A のところは、アーキテクチャによって異なる。
amd64 なら 6、386 なら 8 となる。
※ インストール手順に arm の場合は 8 および 5 と書かれているが、試していないのでどういうことかよくわからない。

提供されているサンプルソースをコンパイル/リンクし、正常に動作することが確認できた。

package main
import "fmt"
func main() {
fmt.Printf("hello, world\n")
}

これを test.go として保存し、コンパイル&リンク。

$ 8g test.go
$ 8l test.8
$ ./8.out
hello, world


とりあえず、今回はここまで。
タグ:Googe Go
posted by K/I at 21:18 | 東京 ☀ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする

2009年11月08日

文字を画像として出力する (PHP)

文字を画像にして出力するPHPスクリプトを作ってみた。

ターゲット端末に必要なフォントがインストールされていない場合などに使える。

<?php
if (isset($_REQUEST['c1'])
&& isset($_REQUEST['c2'])
&& isset($_REQUEST['c3'])) {
eval("\$c1 = 0x{$_REQUEST['c1']};");
eval("\$c2 = 0x{$_REQUEST['c2']};");
eval("\$c3 = 0x{$_REQUEST['c3']};");
$char = sprintf('%c%c%c', $c1, $c2, $c3);
$font = './font1.ttf'; // TTF フォントファイルを指定する
$font_size = 20; // フォントサイズ
$bbox = imagettfbbox($font_size, 0, $font, $char);
$dh = $bbox[1];
$x = $bbox[0];
$w = abs($bbox[4] - $bbox[0]);
$h = abs($bbox[5] - $bbox[1]);
$im = imagecreate($w, $h);
$black = imagecolorallocate($im, 0, 0, 0);
$white = imagecolorallocate($im, 255, 255, 255);
imagefilledrectangle($im, 0, 0, 299, 299, $white);
imagettftext($im, $font_size, 0, -$x-1, $h-$dh-1, $black, $font, $char);
header('Content-type: image/png');
imagepng($im);
imagedestroy($im);
exit;
} else {
header('Content-Type: text/html');
?>
<html>
<head>
<title>文字を画像で出力</title>
</head>
<body>
<form method="test.php" name="form1" mthod="get">
<div>UTF-8 コード:
<input type="text" name="c1" value="" size="2" />&nbsp;
<input type="text" name="c2" value="" size="2" />&nbsp;
<input type="text" name="c3" value="" size="2" /><br />
<input type="submit" value="変換" /></div>
</form>
</body>
</html>
<?php
exit;
}
?>

$font 変数に、True Type フォントのファイル名を、$font_size 変数に表示するフォントのサイズを指定し、test.php というファイル名でしかるべき場所に保存する。

何もパラメータを指定せずに実行すると入力欄が3つ表示されるので、そこに1バイト分ずつ UTF-8 での文字コードを入力する。
たとえば、「石」であれば e79fb3 とする。
[変換] ボタンをクリックすれば、指定した文字が画像として表示される。

なお、いつものことながら上のコードサンプルではセキュリティ上の配慮はしていない。

imagettfbbox() は、指定したフォントサイズ、角度 (この場合は0°)、フォント、文字列 (この場合は1文字だけ) で bounding box を作成してその四隅の座標を配列で返す関数。
その座標情報をもとに画像サイズ (imagecreate() 関数) と文字の位置 (imagettftext() 関数) を決めている。

imagettftext() 関数で 4 つめと 5 つめの引数で -$x-1$h-$dh-1 のように 1 を引いているが、これはフォントによって調整が必要かもしれない。
posted by K/I at 11:22 | 東京 ☀ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする

2009年11月03日

RSSを読み込んでTwitterでつぶやく (Perl)

RSSフィードを拾い出して、Twitter にその記事情報を投稿するしくみを Perl で作ってみたのでご紹介。
※ただし、まだ動作に問題があり、改善が必要。

最近のブログには、投稿すると自動的にツイッターに記事の投稿情報を投げてくれるものもあるが、それに対応していない場合に使え そうだ。
また、RSS Reader と Twitter の両方見るのが面倒、という場合は Twitter を RSS リーダー代わりに使う、という用途もありうる。
ただ、無判断にすべて引用してしまうことになるので、ちょっと問題があるかもしれないが。

#!/usr/bin/perl
use strict;
use utf8;
use Encode::JP;
require LWP::UserAgent;
use XML::RSS::Parser;
use Net::Twitter;
my $ua = LWP::UserAgent->new;
my $rss_p = XML::RSS::Parser->new;
# 一覧ファイル読み出し
# ユーザー名<>パスワード<>タイプ<>RSSのURL<>サイトタイトル
# という書式
my $data_file = 'data.txt';
open DATA, $data_file;
flock DATA, 1;
while (my $rss = <DATA>) {
# 項目
$rss =~ s/(?:\r\n?|\n)$//;
next if $rss =~ /^#/; # コメント行は無視
my @rss_items = split /<>/, $rss;
# RSS 読み込み
my $response = $ua->get($rss_items[3]);
$response->is_success or next;
my $rss_data = $response->content;
# RSS のパース
my @articles;
if ($rss_items[2] eq 'type1') {
@articles = parser_type1($rss_p, $rss_data);
} else if ($rss_items[2] eq 'type2') {
@articles = parser_type2($rss_p, $rss_data);
}
# type3 以降も必要に応じて記述
my $tw = Net::Twitter->new(traits => [qw/API::REST/],
username => $rss_items[0],
password => $rss_items[1]);
foreach my $ar_article (@articles) {
my @article = @$ar_article;
my $message = "${article[0]} (${rss_items[4]}) ${article[1]}";
print STDERR Encode::encode('utf-8', "${message}...");
# Twitter に投稿
my $result = $tw->update($message);
print STDERR "done.\n";
sleep 30;
}
}
close DATA;
sub parser_type1
{
my ($rss_p, $rss_data) = @_;
my @articles = ();
my $rss_feed = $rss_p->parse_string($rss_data);
foreach my $item ($rss_feed->query('/channel/item')) {
my $dt_title = $item->query('title')->text_content;
my $dt_link = $item->query('link')->text_content;
push @articles, [ $dt_title, $dt_link ];
}
return @articles;
}
sub parser_type2
{
# 別のタイプのRSSパーサーを記述
}
1;
data.txt というファイルに、<> 区切りでユーザー名やパスワード、RSS のパスなどを指定してコマンドラインでこのスクリプトを実行すれば、Twitter に投稿される。

Twitter とのインターフェースには Net::Twitter というモジュール (CPAN から入手できる) を用いたが、ほかのモジュールもあるし、コマンドラインで curl を呼び出してもよい。
Things Every Developer Should Know
8) A command line is all you need to use the Twitter API
http://apiwiki.twitter.com/Things-Every-Developer-Should-Know#8AcommandlineisallyouneedtousetheTwitterAPInbsp

また、ループの最後で sleep 30 をしているが、これは Twitter のレート制限への対応である。
Rate limiting
http://apiwiki.twitter.com/Rate-limiting
1アカウントにつき、1時間に150回まで、つまり24秒に1回までという制限があるので、少し多めに、30秒間スリープしている。

前述のとおり、このコードにはいくつか問題があり、改善の必要がある。

まず、RSS の読み込み時に文字化けすることがある。
文字化けするとそれ以降すべて文字化けしてしまうようだ。
原因はまだよく検討していない。
なお、この問題は PHP で同様のコードを書くと生じないので、単に Perl でのエンコーディングの処理の問題。

また、140字の文字数制限を考慮していないので、その文字数をオーバーしている場合にはエラーを起こして途中で終了してしまう。
特に、記事のURLが長い場合もあるだろうから、対策にはちょっと工夫が必要だ (bit.ly などの URL 短縮サービスを使うなど)。

それから、どの記事を Twitter に上げたか管理していないので、処理に時間がかかってしまう。
(重複した記事は投稿されず、Twitter 側で捨ててくれるようだ)
重複投稿を避けるには、どの記事を投稿したかデータベースで管理するなど、何らかの仕組みが必要だ。
タグ:RSS twitter Perl
posted by K/I at 18:29 | 東京 ☀ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする

2009年10月26日

自動翻訳 - Google AJAX & エキサイト

馴染みのない分野の翻訳を頼まれて、しかも納期が厳しかったので自動翻訳のお世話になった。
実務に使ったのははじめてだ。
使用したのはフリーのサービスのみなので、翻訳品質はとても悪く、下訳にすらならない。
最初の大意をつかむための補助ツール程度。
ただ、慣れない分野の場合なら、全体の意味をざっととる手間が省けてかなり助かるし、単語レベルで訳語を拾い出すこともできて重宝だ。
有料の翻訳ツール、特に分野を特化したものならもっと良質の訳文が出てくるのかもしれないが、今のところ考えていない。

さて、フリーの自動翻訳サービスはいくつかあるが、実際に使ったのは Excite のものだ。
http://www.excite.co.jp/world/english/
対応言語数が多く、業務以外で時おり使わせてもらっている。

もう1つ試したのが Google の API。
日本語:

 
  (訳文はこちらに表示されます)
 

こちらは API の形式で提供されているので、上のように好きなところにはめこんで使えるところが良い。

今回試したのはこの 2 つだけだが、翻訳品質としては、Excite のほうがずっと良いようだ。
分野にも依るかもしれないが。
タグ:google JSAPI 言語
posted by K/I at 18:34 | 東京 ☔ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする

2009年09月26日

Yet Another Haskell Tutorial (和訳): 4.4 関数型

4.4 関数型

Hakell では、関数はれっきとしたクラス値である。つまり、1'c' が型を持つ値であるのとまったく同じである。これは、square++ といった関数についても言える。関数についてあれこれ述べる前に、少し横道に逸れてごく理論的なコンピュータサイエンスの分野に分け入って (それほど苦痛なものにはならないと思うのでご心配なく)、ラムダ計算について述べておく必要がある。

4.4.1 ラムダ計算

“ラムダ計算”という名称は、もしかするととっつきにくい感じを受けるかもしれないが、関数を表現するきわめて単純なシステムを表している。ラムダ計算風に平方関数を書くと、λx.x*x のようになる。これは、値を1つ取り、それを x と呼び (それが“λx.”の意味である)、x 同士を掛け合わせる、という意味だ。λは“ラムダ抽象”と呼ばれる。λはふつう、パラメータを1つだけ取ることができる。2つの数をとる関数を書きたいなら、はじめの部分をもう1つ用意して2番目に加え、λxλy.2*x+y のように書く。ラムダ式に値を適用するときは、一番外側のλを取り除き、ラムダ変数をすべて値に置き換える。たとえば (λx.x * x)5 を計算する場合、λを取り除き、x をすべて 5 に置き換え、(5 * 5) つまり 25 となる。
実際、Hakell はラムダ計算の拡張を元にしているところが多く、この2つの式は Hakell で直接記述できる (λをバックスラッシュに置き換え、. を矢印に置き換える。また、λは繰り返し書く必要は無い。そしてもちろん、Haskell では、関数を定義すればそれに名前をつけることができる)。

square = \x -> x*x
f = \x y -> 2*x + y

ラムダ式を対話シェルで計算することもできる。

Prelude> (\x -> x*x) 5
25
Prelude> (\x y -> 2*x + y) 5 4
14


2つめの例では、ラムダ抽象に引数を 2 つ (1つは x、もう1つは y) 与える必要があることがわかる。

4.4.2 高階型

関数には“高階型”という名称が与えられている。関数に与えられる型は、関数のラムダ計算の表現によく似ている。たとえば、平方は、λx.x * x と定義される。この型は、まず x の型が何であるかを考えてみるとわかる。たとえば xInt であったとしよう。すると、関数 squareInt を引数に取り、値 x*x を生成することがわかる。2つの整数値を掛け合わせると Int になることがわかっているので、squre の結果の型も Int である。ゆえに、squre の型は、IntInt であると言う。
前述の関数 f についても、同じように考察できる。この関数の値 (関数は値であることを思い出してほしい) は値 x を引数に取り、その値から新しい値を生成する。その値は値 y を引数に取り、2*x+y を生成する。たとえば f の場合、数値を 1 つだけ適用したとすると、(λxλy.2x + y)5 となり、新しい値 λy.2(5) + y となる。ここでは、x すべてを適用した値 5 で置き換えている。
だから、f は Int を引数に取り、何らかの型 (それが何だかはっきりしないが) の値を生成することは知っている。しかし、この値の型は λy.2(5) + y の型であることはわかっている。上記の考察を当てはめると、この式は IntInt という型をもつことがわかる。ゆえに、fInt を引数に取り、IntInt という型を持つ何かを生成する。だから、f の型は Int → (IntInt) である。

■注■ 括弧は必須ではない。関数型では、α → β → γ であれば β → γ がグループ化されているとみなされる。もし、α → β がグループ化されているとしたい場合は、そこを括弧で囲む必要がある。

これは完全に正確なわけではない。以前述べたとおり、5 のような数値は、実際には Int 型ではなく、Num a ⇒ a 型である。
Prelude 関数の型は、前述のとおり、":t" を使えば容易にわかる。

Prelude> :t head
head :: [a] -> a
Prelude> :t tail
tail :: [a] -> [a]
Prelude> :t null
null :: [a] -> Bool
Prelude> :t fst
fst :: (a,b) -> a
Prelude> :t snd
snd :: (a,b) -> b

これは次のような意味である。"head" は型 "a" の値を含むリストを 1 つ引数に取り、型 "a" の値を返す関数。"tail" は "a" のリストを 1 つ引数に取り、"a" のリストを返す。"null" は "a" のリストを 1 つ引数に取り、真偽値を返す。"fst" は型 "(a,b)" のペアを 1 つ引数に取り、型 "a" の何かを返す。などである。

■注■ fst の型が (a, b) → a であっても、必ずしも単純に最初の要素を返すことを意味するわけではない。返す値が最初の要素と型が同じであることを意味するだけである。

+*++: のような演算子の型を得ることもできる。ただし、括弧でくくる必要がある。ふつう、二項演算関数として使う関数 (つまり、2 つの引数の前ではなく、間に置く関数) はすべて、型を調べるときには括弧でくくる必要がある。

Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> :t (*)
(*) :: Num a => a -> a -> a
Prelude> :t (++)
(++) :: [a] -> [a] -> [a]
Prelude> :t (:)
(:) :: a -> [a] -> [a]

+* の型は同じで、+Num のインスタンスのある型 a に対し、型 a の値を 1 つ引数に取り、型 a の値を 1 つ引数に取って型 a の値を生成する別の関数を生成する。簡潔に言うなら、+ は型 a の値を 2 つ引数に取り、型 a の値を生成するとも言えるが、やや正確さに欠ける。
++ の型の意味は、簡潔に言えば、与えられた a に対し、++a のリストを2つ引数に取り、新たな a のリストを生成する、ということだ。同様に、: は型 a の値1つと型 [a] の別の値 (つまり a のリスト) 1つを引数に取り、型 [a] の別の値を生成する。

4.4.3 煩わしい型 IO

putStrLn といった関数の型を調べようと思うかもしれない。

Prelude> :t putStrLn
putStrLn :: String -> IO ()
Prelude> :t readFile
readFile :: FilePath -> IO String

いったい、この IO というのは何だろうか? 簡単にいってしまえば、関数が本当の関数ではない時、Haskell はこういう表現をするのだ。これを、"IO アクション" (以後、IO) と言う。すぐにこんな疑問が生じるだろう。それはそうと、IO を出させないようにするにはどうすればよいのだろうか? 手短に言うと、直接取り除くことはできない。つまり、IO String → String 型の関数を書くことはできない。IO 型の物を使うには、たとえば do 記述を使って別の関数と結びつけるしかない。
たとえば readFile を使ってファイルを読み込んでいるとき、おそらく、返される文字列に対して何かをしたいと思うだろう (そうでなければ、そもそもなぜファイルを読み込んだのだろう?)。String を引数に取り Int を生成する関数 f があるとしよう。f の入力は String だが、readFile の出力は IOString で一致しないため、readFile の結果を直接 f に入れることはできない。しかし、以下のようにすると結びつけることができる。

main = do
s <- readFile "somefile"
let i = f s
putStrLn (show i)

ここで、矢印記号を使って "IOアクションから文字列を取り出し"、その文字列 (s としている) に f を適用している。そして、たとえば i を画面上に出力する。ここで使われている let には対応する in が無いことに注意してほしい。これは、do ブロックの中だからである。また、f はIOアクションではなく単なる通常の関数なので、i <- f s とは書かないことにも注意が必要である。

4.4.4 明示的な型定義

以下のような理由で、ある要素や関数の型を明示的に指定したい時がある。
  • 明瞭性
  • 速度
  • デバッグ
トップレベルの関数は必ず型を指定するのがソフトウェア・エンジニアリング上正しいと考える人もいる。少なくとも、プログラムのコンパイル時に型エラーが出て理由がよくわからない時は、いくつかの関数の型を明示的に定義するとエラーがどこにあるのかわかりやすくなるかもしれない。
型定義は関数定義とは別に記述する。たとえば、以下のようなコードで square 関数の型を指定できる (明示的に定義した型を 型シグネチャ と言う)。

square :: Num a => a -> a
square x = x*x

この2行は、続けて記述する必要すらない。ただし、指定する型は、関数定義から推測される型と一致しなければならない。この定義では、square は Num のインスタンス (Int、Double、など) の平方が適用できる。ただし、squareInt型の値にだけ適用されるという前提条件を知っていれば、その型を以下のように精錬することができる。

square :: Int -> Int
square x = x*x

これで、squre は Int 型の値にのみ適用できる。さらにこの定義では、squre には Int 型の値しか適用しないことがわかっているので、コンパイラは変更前の定義で指定していたような一般的なコードを生成する必要が無く、実行速度の速いコードを生成することができるだろう。
もし、拡張機能を有効にしてあるならば (Hugs では "-98"、GHC(i)では "-fglasgow-exts")、関数だけでなく、拡張機能にも型シグネチャを追加できる。たとえば、以下のように書くことができる。

square (x :: Int) = x*x

この定義は、xInt であることをコンパイラに教えるが、式の残りの部分の型はコンパイラの推論に任されている。この例の squre の型は何だろうか? まずは考えてみよう。答えはこのコードをファイルに入力してインタープリタにロードするか、式の型を尋ねてみれば分かる。

Prelude> :t (\(x :: Int) -> x*x)

このラムダ抽象は上記の関数定義と同等である。

4.4.5 関数引数

第3.3節で、関数を引数に取る関数の例を考察した。たとえば、map は、関数を1つ引数に取り、その関数をリストの各要素に適用する。filter は、リストのどの要素を残すかを判定する関数を1つ引数に取る。foldl は、リストの要素の結合方法を与える関数を1つ引数に取る。Haskell のほかの関数と同様、これにもきちんとした型がある。
まず、map 関数について考えてみよう。map の役割は要素のリストを引数に取り、別の要素のリストを生成することだ。この2つのリストは、必ずしも同じ型の要素を持つ必要はない。だから、mapは、型 [a] の値を引数に取り、型 [b] の値を生成する。map 関数は、それをどのように行っているのだろうか? ユーザーが提供する関数を用いて変換を行っている。1 つの a を 1 つの b に変換するためには、この関数の型は a → b でなければならない。ゆえに、map の型は(a → b) → [a] → [b] である。これはインタープリタの ":t" で確かめられる。
同様の考察は filter にも当てはまり、(a → Bool) → [a] → [a] という型を持つことがわかる。foldl 関数については、(a → a → a) → a → [a] → a という型だと考えたくなるかもしれない。実際 foldl は、より一般的な型 (a → b → a) → a → [b] → a を持っている。だから、1 つの a と 1 つの b を 1 つの a に変える関数 1 つと、型 a の 初期値 1 つと、型 b のリスト 1 つを引数に取る。これは型 a を生成する。
これを確かめるには、与えられた制約を満たすリストのメンバーがいくつあるかを数える関数 count を書けばよい。もちろん filterlength を使ってもよいが、foldr を使うこともできる。

module Count
where
import Char
count1 p l = length (filter p l)
count2 p l = foldr (\x c -> if p x then c+1 else c) 0 l

count1 の機能は単純だ。述語 p に従ってリスト l にフィルターをかけ、その結果得られたリストの長さを引数に取る。一方、count2 は初期値 (これは整数値である) を使って現在のカウント値を保持する。count2 は、リスト l の各要素に対して、上記のラムダ式を適用する。このラムダ式は 2 つの引数を取る。現在のカウント値 c と、リスト中の対象となる要素 x である。ラムダ式は、xp を満たすかどうかを調べる。もし満たすなら、述語を満たす要素の数を 1 つ増やして c+1 を返す。満たさないなら、そのまま c を返す。

演習

演習4.3 以下の式を自分で計算せよ。また、もしそれが型を持つなら、その型を確かめよ。式が型エラーを起こすかどうかにも注意すること。

1. \x -> [x]
2. \x y z -> (x,y:z:[])
3. \x -> x + 5
4. \x -> "hello, world"
5. \x -> x 'a'
6. \x -> x x
7. \x -> x + x


前ページ「4.3 型クラス
次ページ「4.5 データ型


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 19:11 | 東京 ☁ | Comment(2) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする

2009年08月23日

ルーター RV-S340NE を電話機から設定

前回の記事「ルーター RV-S340NE で IP マスカレード」の続編。

Web 関連の実験のために、DDNS サービスを利用して自宅のプライベートネットワーク内のサーバーをインターネット上に公開している。
先日の引越しで利用することになった RV-S340NE には、ファームウェアを自動更新する機能がついているのだが、更新時には再起動がかかり、WAN 側 IP アドレスが変わってしまって都合が悪い。
(自分が利用している DDNS の仕組み上の問題ではある)

そのため、ファームウェアの自動更新機能を OFF にすることにした。
取扱説明書には Web 設定画面上で設定変更できるように書かれているが、実際には設定できないようだ。
何らかの理由で除外されたのだろうか。

RV-S340NE は、電話機から各種設定が可能なので、それを試してみることにした。

まず、電話機の受話器を取り上げる。(「ツー」という音がする)
そして、電話機のダイヤルボタンでコマンドを入力する。
たとえば、ファームウェアを手動更新する(再起動更新有効)なら、
***889*1##
とボタンを押すと、しばらくして「設定が完了しました」というガイダンスが流れて電話が切れる。

何やら不思議な感動を覚える。。。
posted by K/I at 18:17 | 東京 ☀ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする

ルーター RV-S340NE で IP マスカレード

今まで KDDI メタルプラス の ADSL を使っていたが、転居を機にフレッツ光に変更した (転居先が、メタルプラスを使用できなかったため)。

ひかり電話ルータ RV-S340NE でポートマッピング (IP マスカレード) を設定して、外から自宅 Web サーバーに接続できるようにしたのだが、このルーターの設定画面にはちょっと癖があるのでご紹介しておきたい。

設定はブラウザから行える。
ルーターの初期設定の IP アドレスは 192.168.1.1。
ルーターを DHCP サーバーとして IP アドレスを決定した場合は、http://ntt.setup/ で Web 設定画面にアクセスできる。

さて、IP マスカレードの設定は、左メニューの[詳細設定]→[静的IPマスカレード設定]から行える。
RV-S340NE 設定画面 #1
設定一覧の右側にある[編集]リンクをクリックすると、「静的IPマスカレード設定 エントリ編集」画面が開く。
たとえば、80 番ポートへの接続をローカル IP 192.168.1.2 のマシンに振りたければ、以下のように入力し、[設定]ボタンをクリックする。
RV-S340NE 設定画面 #2
[前のページへ戻る]ボタンをクリックすると、設定したエントリがNATエントリ一覧に追加されている。
RV-S340NE 設定画面 #3
ところが、この状態ではこの設定は有効になっていないので注意が必要だ。
エントリ番号の左側のチェックボックスにチェックマークを入れ、下の[設定]ボタンをクリックするとはじめて有効になる。
RV-S340NE 設定画面 #4

なお、左上でオレンジ色の[保存]ボタンが点滅して自己主張しているが、これをクリックしなくても設定は反映される。
ちょっと紛らわしいが、この[保存]ボタンはルーターの電源を OFF したり、再起動したりしても設定が消えないように "保存" するためのものと思われる。
RV-S340NE 設定画面 #5
posted by K/I at 17:46 | 東京 ☀ | Comment(0) | TrackBack(0) | 技術メモ | このブログの読者になる | 更新情報をチェックする