2012-04-07

【Lisp/Scheme】Pythonで実装されたSchemeインタプリタをPHPにコンバートしてみた。

関数型言語勉強の一環として、比較的簡単なSchemeのインタプリタをいつも使ってるPHPで実装してみることを考えたのですが、参考になる書籍がなかったのでネットで探したところ以下のページを見つけました。
((Pythonで) 書く (Lisp) インタプリタ) http://www.aoky.net/articles/peter_norvig/lispy.htm

このページのプログラムはPythonで書かれています。PythonとPHPはかなり違う言語なので、なぜかPythonの勉強もしてしまいましたw

評価器以外には特に苦労しなかったので、評価器以外を直接見たい場合はこの記事一番下からダウンロードしてください。

■評価器(Evaluator)
評価器は標準入力から入力されたS式(Lisp系言語のプログラムのこと)を読取り器から得るシンボルや定数の列を受け取って解釈しプログラムを実行するものです。
以下具体的に見ていきます。実際のPythonプログラムは上のページを見てください。
評価器は再帰関数になってて、Schemeのスペシャルフォーム(評価器が式の内容全てを評価しない式のこと)の処理と、普通の式を処理するかなり簡単なプログラムです。
が、これをPHPにコンバートするのに苦労しました。以下が自分の書いた評価器プログラムです。

・コンバートしたPHPプログラム
/**
  定数、シンボルまたはシンボルの配列を評価する。再帰関数。
  @param $symbols 定数、シンボル、またはシンボルの配列
  @param $env     環境オブジェクト。指定されない場合はトップレベル環境。
*/
public static function lisp_eval( $symbols, $env = null )
{
  // 環境が指定されていないときはトップレベルの環境を指定
  if( $env == null ) {
    global $global_environment;
    $env = $global_environment;
  }
  
  // シンボルの場合
  if( is_a( $symbols, 'Symbol' ) ) {
    return $env->get( $symbols->value );
  
  // 定数の場合
  } elseif( is_scalar( $symbols ) ) {
    return $symbols;
  
  // 以降リストの場合
  } elseif( $symbols[ 0 ]->value == 'quote' ) {     // (quote exp)
    list( $quote, $exp ) = $symbols;
    return $exp;
    
  } elseif( $symbols[ 0 ]->value == 'if' ) {        // (if test conseq alt)
    list( $if, $test, $conseq, $alt ) = $symbols;
    if( self::lisp_eval( $test, $env ) ) {
      return self::lisp_eval( $conseq, $env );
    } else {
      return self::lisp_eval( $alt, $env );
    }
    
  } elseif( $symbols[ 0 ]->value == 'define' ) {      // (define var (lambda exp))
    list( $define, $var, $exp ) = $symbols;
    $env->add( $var->value, self::lisp_eval( $exp, $env ) );
    
  } elseif( $symbols[ 0 ]->value == 'lambda' ) {      // (lambda vars exp)
    list( $lambda, $vars, $exp ) = $symbols;
    $str_lambda  = '$args = func_get_args();' . "\n";
    $str_lambda .= '$env  = array_pop( $args );' . "\n";
    $str_lambda .= '$vars = unserialize( stripslashes( ' . "'" . addslashes( serialize( $vars ) ) . "'" . ' ) ); ' . "\n";
    $str_lambda .= '$exp  = unserialize( stripslashes( ' . "'" . addslashes( serialize( $exp ) ) . "'" . ' ) ); ' . "\n";
    $str_lambda .= '$new_env = new Environment( &$env );' . "\n";
    $str_lambda .= '$new_env->zip( $vars, $args );' . "\n";
    $str_lambda .= 'return Evaluator::lisp_eval( $exp, $new_env );' . "\n";
    $lambda_function = create_function( '', $str_lambda );
    return $lambda_function;
  
  } elseif( $symbols[ 0 ]->value == 'begin' ) {       // (begin exps)
    each( $symbols );
    foreach( $symbols AS $symbol ) {
      $val = self::lisp_eval( $symbol->value, $env );
    }
    return $val;
    
  } else {                                            // 関数評価
    $exps = array();
    
    foreach( $symbols AS $symbol ) {
      $exps[] = self::lisp_eval( $symbol, $env );
    }
    $proc = array_shift( $exps );
    
    /*
      小細工
      関数が使う環境オブジェクトを関数の引数として渡す。
    */
    array_push( $exps, $env );
    $ret  = call_user_func_array( $proc, $exps );
    return $ret;
  }
}


Pythonの評価器には「S式をリスト構造に直した」リストと、環境オブジェクト(後述)が渡されるようですが、PHPにはリストのように扱える組み込みデータ構造が用意されていません。
この関数のためにいちいちリスト構造を実装するのも面倒なので、配列をリストの代わりにしました。
引数の「$symbols」がその配列です。この配列に対して「each」「array_shift」「list」などを使えば、案外簡単に配列をリストのように扱えます。

・環境オブジェクトのこと
環境オブジェクト(以下環境)はトップレベルやレキシカルな環境を実現するためにあります。評価器に渡される環境オブジェクトには現在自分がいる階層の変数、関数が見えています。
また、環境には1つ外側の環境のポインタを持っていて、「あるシンボルを参照したいときに自分のいる環境に無い場合はひとつ外側の環境を参照する」という再帰メソッドが定義されています。
自分のいる階層の環境には自由に関数やシンボルを登録することができますが、自分より下(よりレキシカルな)環境にはアクセスできません。

・関数定義と環境オブジェクトに関する小細工
評価器にはS式のリストと環境が渡されます。リスト内に関数定義があると、評価する際微妙な問題を起こしました。
Pythonのプログラムは普通にクロージャ内に直接実行時の環境を渡せるのですが、自分が使っているPHP(Ver5.1.4)のクロージャには現在の環境をそのままでは渡せません。
なので、関数実行時に「関数の引数とともに現在の環境を押し込む」というなんともチカラ技なプログラムになってしまいました。
そんな感じなので、クロージャでは「引数のチェック」なんてものはしてませんw

こんな感じです。


Pythonのプログラムページに載っている以下のプログラムも「遅い」ですけど、一応動作します。
lis.py> (define area (lambda (r) (* 3.141592653 (* r r))))
lis.py> (define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1)))))) lis.py> (define first car)
lis.py> (define rest cdr)
lis.py> (define count (lambda (item L) (if L (+ (equal? item (first L)) (count item (rest L))) 0)))


PHPのソース全部は以下からダウンロード可能です。
http://www.tagajo.tv/lisphp_1.0.zip

■使い方
シェルに以下のように入力してください。
$ php -f lisphp.php

2012-03-14

【Androidアプリ】微速度撮影カメラ作ってみた。

Androidスマートフォン(Infobar A01)を買いました。
このスマートフォンにはカメラもついています。そこで、Androidアプリ開発になれるためにほしかった「微速度カメラ」を作ることにしました。

この「微速度撮影機能(インターバル撮影ともいいます)」とは、設定した間隔で自動でシャッターを切ってくれる機能のことで撮影した画像ファイルをぱらぱら漫画の要領で映像に変換すると早回し編集をしなくても面白い映像を作ることができます。
テストで撮影した動画は以下の通りです。



・仕様
対応機種  :Infobar A01 (他にテストできる実機がないため)
対応OSVer :2.3.3(他にテストできる実機がないため)
画面の向き:横のみ
画像保存場所:SDカード内、「DCIM/lapse」以下に保存
画像ファイル名:「yyyymmdd_hhmmss.jpg」

・使い方
1)「設定変更」ボタンをクリック
2)「画像解像度」、「シャッター間隔」、「撮影時間」を設定して「設定する」ボタンをクリック。設定は保存され、次回起動時には前回起動時の設定に自動的に変更されます。
3)「設定する」ボタンをクリックすると、自動的に前の画面に戻りますので「撮影開始」ボタンクリックで撮影開始します。
4)撮影を途中でやめるときは「撮影終了」ボタンをクリックします。


・スクリーンショット
撮影画面:
 設定画面:

・機能
1)スマートフォンのカメラに対応した画像サイズを設定可能
2)シャッター間隔は5、10、15、30、45、60、90、120秒に設定可能
3)撮影時間は1、5、10、15、30、45、60、90、120、180、240分に設定可能

・ダウンロード
Gumroad 微速度撮影カメラアプリ
Androidマーケット 微速度撮影カメラアプリ

・プログラム作成時に躓いたところ
1)ActivityクラスがJavaアプレットのようにライフサイクルによりイベントが発生する形式なので、Cameraクラスのメソッドreleaseを実行した後にtakePictureを実行してしまったりした。
2)Activityクラスは画面の傾きにより縦と横に向きが変わるが、そのたびに「onDestroy」と「onCreate」が発生しめんどくさい。
3)「メソッドの引数」と「クラスのメンバ」が「同じ名前」のときに、無名クラスからその名前を使うときは、引数にfinal修飾子が必要なこと
4)無名クラスから、その無名クラスを定義しているクラスの参照したいときには「無名クラスを定義しているクラス名.this」と書くこと
5)ぱっと見、スタックトレースやエラーメッセージの見方がわからないこと
6)実装の都合からActivityが1つしかないので、AlertDialogすら出すのに苦労すること。
7)TableLayoutで使うTableRowには「行の結合」ができないこと。

2012-02-22

【Gumroad】プログラム販売をしてみることにした

Gunroadというサービスがあります。
このサービスは「ファイルをアップロードする」あるいは自分のサーバ上にファイルを置いてその「URLを登録するだけ」でプログラムを販売するページが作成できる、というものです。
サービスへ払う必要がある手数料は「商品金額の5%+$0.30」ということになっています。
詳細はインプレスのInternet Watchの記事↓を参照してみてください。
http://internet.watch.impress.co.jp/docs/special/20120215_512149.html

とりあえず、自分が作ったPHPの自作ライブラリを販売していってみることにしました。
使い方はファイルの最初に「コメント」として記載しておきました。ちょっとしたサンプルプログラムも載っています。

1つ目:PHPのMySQL接続&結果セットクラス
2つ目:PHPカレンダー作成クラス
3つ目:Linuxサーバで自動的にFTPダウンロードするシェルスクリプト

2つ目は使い方がわからなくて「2ドル」の価格設定としてしまいましたが、1つ目の方は「200円」にしています。
さて、どのくらい売れるでしょうか。

それでは。

2012-02-10

【Apache】Rewriteの設定方法

タイトルの通りなんですが、いわゆる「静的ページ(この言い方どうもしっくり来ないのは自分だけなのでしょうか?)」でWebページを構成したいときにも使います。
その設定方法についての説明です。

例としてAmazonのAPIを使用した商品紹介ページを考えます。

■仕様
・「本」カテゴリの「TIGER&BUNNY(タイガー&バニー) ~桂正和原画&ラフ画集成~」という商品のページ
・「本」カテゴリのSearchIndexは「Books」とする。
・「TIGER&BUNNY(タイガー&バニー) ~桂正和原画&ラフ画集成~」当商品のASINは「408908153X」とします。






■設計
仕様から、以下のようなURLを考えます。

1)パラメータつきのURL
  http://www.foo.com/item.php?s=books&a=408908153X

2)パラメータなしのURL
  http://www.foo.com/item/books/408908153X

今流行のURL形態にしようと思います。

■実装
実装は以下のような感じです。
1)「item.php」上のAタグ、HREF属性を上のパラメータなしURLに変更する。
2)www.foo.comドメインのトップディレクトリにある「.htaccess」にRewriteの設定を記述する。

■Rewriteの設定
やっと本番です。今の例の場合「.htaccess」は以下のようになるでしょう。

RewriteEngine on                                     // Rewrite設定有効化
RewriteCond %{REQUEST_FILENAME} !-f                  // リクエストに「実体」がある場合それを優先する
RewriteCond %{REQUEST_FILENAME} !-d                  // リクエストに「実体」がある場合それを優先する
RewriteRule /item/([a-zA-Z]+)/([0-9a-zA-Z]+)$ /item.php?s=$1&a=$2 [L]  // 実際の動作

RewriteRuleの「[a-zA-Z]」や「[0-9a-zA-Z]」、「()」「+」「$」は正規表現というものです。
意味は
・[a-zA-Z] → 「小文字のa~zまでと大文字のA~Zまでのどれか1文字」を表す
・[0-9a-zA-Z] → 「数字0~9、小文字のa~zまでと大文字のA~Zまでのどれか1文字」を表す
・() → 括弧で括られた部分をキャプチャして左から順に$1、$2という風に後から使えるようにする。
・+ → 直前のパターンが1文字以上続く、という条件
・$ → 行末の意味
などです。これらは「メタ文字」といいますが、詳しくは専門書でも見てください。説明が大変なんですw


これで多分、動くようになると思いますが実際はよくテストを行い確認をして使ってください。

2012-01-13

【マンション管理士】合格発表と模範解答についての感想

2011年度のマンション管理士試験合格発表と模範解答の発表が今日(2012年01月13日)にありました。
合格点は36点(50点満点、5問免除者は31点)合格率は9.3%となりました。そこで、感じたことをつらつら書いてます。

■雑感
今回のマンション管理士試験は簡単だった前回の試験と比べかなり難しかった、というコンセンサスが取れていたように思われます。
ですが、その割には合格率が「9.3%」と史上初めて9%を超えるという現象が起きました。
これは、今年度のマンション管理士試験受験生のレベルが相当に高かった、ということなのではないかと思います。

■解答速報と資格学校について
前のブログエントリである、「【マンション管理士】試験の感想など」でも記載していたとおり、解答の意見が割れておりました。
掲示板やmixiのコミュニティでも活発な予想合戦が繰り広げられていたようです。そして、結果的には「大手資格学校(LEC、TAC)の失態」と「住宅新報社の一人勝ち」ということに落ち着いたようです。
住宅新報社は1番目の解答速報では、模範解答と異なるものを発表していたようですが、すぐに修正され模範解答と同じものを発表しなおしました。
ほかの大手資格学校が模範解答と異なる解答速報を出す中、孤高だったと言っていいでしょうw

■tagajoの試験結果について
さて、今回の私の成績ですが、模範解答で採点しなおした結果、

32点(免除分を加算すると37点)

というわけで、合格いたしました!
なんとかマンション管理士試験を卒業できました。前年度の試験は簡単だっただけに気持ちの切り替えには苦労しましたが報われた気がしてます。
ちなみに自分の問3、問29、問33の解答は「3-4-2」です。わからなかったのは問3のみだったようです。

それでは。

ZenBack

WebMoney ぷちカンパ