2012年05月26日

第59回PHP勉強会@東京でXMLとかDSLの話をしてきました

昨日、PHP勉強会に行ってきましたので発表に使ったスライドをば。

話した内容はXML_Builderの宣伝と、メソッドチェーンの小手先テクニックとかです。

PHP勉強会で発表するの初めてで、緊張しまくってあんまり内容覚えてないですw

私はDSL大好きなので、sizuhikoさんの紹介されてた「Spec for PHP」は、プリプロセッサを使ってPHPの文法を書き換えてしまい、RubyのRSpecを再現してしまっている(!)変態スペックフレームワークを追いかけてみようと思います。あれはヤバい。

会場を提供してくださいました株式会社クロコス様、ありがとうございました!

タグ:PHP
posted by Hiraku at 14:01 | Comment(0) | TrackBack(0) | 雑記 | このブログの読者になる | 更新情報をチェックする

2012年05月18日

CoffeeScriptで竹内関数を解く

竹内関数といえばベンチマークによく使われる関数です。普通に書くと爆発的な回数の関数呼び出しが発生します。

function tarai(x, y, z) {
  if (x <= y) {
    return y;
  } else {
    return tarai(
      tarai(x - 1, y, z),
      tarai(y - 1, z, x),
      tarai(z - 1, x, y)
    );
  }
}

例えばtarai(10,5,1)なら55229回、tarai(15,5,1)だと実に356426301回もの関数コールが発生します。

竹内関数を高速に解くには、メモ化と遅延評価が知られています。メモ化については解説も多いので、ここでは遅延評価での解き方を考えます。

JavaScriptは先行評価の言語ですので、無理やり遅延評価を行うにはクロージャで引数をラップすることになります。

function tarai(x, y, z) {
    function _tarai(x, y, z) {
        if (x() <= y()) {
            return y();
        } else {
            return _tarai(
                function(){ return _tarai(function(){ return x() - 1 }, y, z)},
                function(){ return _tarai(function(){ return y() - 1 }, z, x)},
                function(){ return _tarai(function(){ return z() - 1 }, x, y)}
            );
        }
    }
    return _tarai(
        function(){ return x },
        function(){ return y },
        function(){ return z }
    );
}

遅延評価版では、関数コールを抑えることができます。たとえばさっきのtarai(15,5,1)でも356426301回から281回に激減します。おそらく一瞬で答えが返ってくるのではないでしょうか。

続きを読む
タグ:CoffeeScript
posted by Hiraku at 00:37 | Comment(0) | TrackBack(0) | JavaScript | このブログの読者になる | 更新情報をチェックする

2012年05月07日

JavaScriptにRuby風のnewメソッドを加える

JavaScriptのオブジェクト指向はクラスベースの皮をかぶったプロトタイプベースです。機能的には十分なのですが、すっきり書く方法が公式に用意されていないので苦労します。一年前に、newを封印してJavaScriptでオブジェクト指向するなんて記事を書いたこともありました。

Rubyではnewは演算子でなくメソッドです。これをインスパイヤしてJavaScriptもnewメソッドを加えてみると、プロトタイプ的継承もすっきり書けるのではないかと思い、試してみました。ECMAScript 5の機能を使っています。当然IE6なんかでは動かないです。

newメソッドその他の定義

Object.defineProperties(Object.prototype, {
    new: {value: function(){
        var self = Object.create(this);
        self.initialize.apply(self, arguments);
        return self;
    }},

    initialize: {value: function(){}},

    instanceof: {value: function(Class){
        function f(){}
        f.prototype = Class;
        return this instanceof f;
    }},

    extends: {value: function(){
        var obj, i, prop, l;
        obj = Object.create(this);
        for (i=0,l=arguments.length; i<l; i++)
            for (prop in arguments[i])
                obj[prop] = arguments[i][prop];
        return obj;
    }},
});
Object.defineProperty(Function.prototype, "new", {value:void 0});

基本的な使い方

上記のようにObject.prototypeを拡張してあれば、ありとあらゆるオブジェクトにnew()メソッドが生えます。オブジェクトをそのままnew()できてクラス(もどき)の定義も簡単に。

/**
 * Birdクラス
 */
var Bird = {
  fly: function(){ console.log("ぱたぱた"); }
};

/* インスタンス化 */
var b = Bird.new();
b.fly(); //ぱたぱた

やっていることはプロトタイプ的継承そのものなのですが、Object.create()を直接使ったり、object()関数を定義したりするのに比べて読みやすいように思います。ちなみに、newの()は省略できないので注意です。

コンストラクタ

initialize()というメソッドを定義しておけば、new()の際に自動的に呼ばれるようにしました。いわゆるコンストラクタです。引数はnew()に渡したものがそのまま渡ります。なお、initialize()の戻り値は見ていないので何もreturnしなくてよいです。

var Bird = {
  initialize: function(name) {
    this.name = name; //インスタンス変数にセット
  },
  fly: function() {
    console.log(this.name + "はぱたぱた飛ぶよ"); //インスタンス変数を引ける
  }
};
var popo = Bird.new("ぽっぽー");
popo.fly(); //ぽっぽーはぱたぱた飛ぶよ

継承、多重継承など

extends()を使うと簡単にオブジェクトを継承できます。プロトタイプベースでは継承とインスタンス化が同義ですので、new()とやっていることの本質は同じです。ただ、initializeを呼ばなかったり、メソッドを上書きするための機構があるなどの違いがあります。

extendsとしましたが、語順が「class クラス名 extends 親クラス名 { ... }」ではなく「var クラス名 = 親クラス名.extends({ ... })」なのでちょっと変かもしれません。(いい単語はないでしょうか?)

//基底クラス Bird
var Bird = {
  initialize: function(name) { this.name = name },
  fly: function() { console.log(this.name + "はぱたぱた飛ぶよ") }
};

//泳ぐ能力のmixin
var SwimSkill = {
  swim: function() { console.log(this.name + "はすいすい泳ぐよ") }
};

//Birdを継承してSwimSkillをmixin、さらにflyメソッドを上書きする
//多重継承させたいオブジェクトがあれば引数に追加(可変長引数)
//後ろに書いた方が上書きしていく
var Penguin = Bird.extends(SwimSkill, {
  fly: function() {
    //親クラスのメソッドを使いたい場合はcallやapplyを利用
    //Bird.fly.call(this);
    console.log(this.name + "は飛べないんだ…");
  }
});

var pen = Penguin.new("ぺんぎんさん");
pen.swim(); //ぺんぎんさんはすいすい泳ぐよ
pen.fly(); //ぺんぎんさんは飛べないんだ…

initializeメソッドは何も上書きしなければ親のものがそのまま使われます。

その他

  • JS本来の文法ではないのでinstanceof演算子は使えないが、代わりにinstanceof()メソッドを実装しておいた。pen.instanceof(Penguin) === trueなど。語順も同じだし読みやすいのではないかと。
  • Object.prototypeを拡張する際はObject.definePropertyもしくはObject.definePropertiesなどを使い、for〜inループに影響を与えないよう配慮すること。(enumerableをfalseにするべし)
  • newやinstanceofといった予約語はプロパティラベルとして使える。(ECMAScript 5thからだっけ?)
  • 組み込みのクラスやコンストラクタも頑張ればnew()メソッドに対応できそうな気がするけど、面倒くさそうなので実装していない。とりあえず既存のクラスは相変わらずnew Dateなどで。(間違って呼ばないようFunction.prototypeからはnewを削除してある。)
  • この方法だとクラスメソッドは定義できない。(全部インスタンスへ継承されてしまう)
  • コンストラクタの自動継承はJS組み込みのオブジェクト指向記法だとできないため、newメソッド方式の利点と言えるかもしれない。

ECMAScript 5だと色々できて楽しいですね。

タグ:javascript
posted by Hiraku at 00:07 | Comment(1) | TrackBack(0) | JavaScript | このブログの読者になる | 更新情報をチェックする

2012年04月06日

PHPでRSSを読むならXML_Builderにおまかせ!

RSSをPHPで読み込む、というよくある処理。PHP5ならSimpleXMLという超簡単にXMLが扱えるクラスがあるので、これを使うと楽です。

var_dump(
  simplexml_load_file(
    'http://blog.tojiru.net/index.rdf'
  )
);
object(SimpleXMLElement)#1 (2) {
  ["channel"]=>
  object(SimpleXMLElement)#2 (4) {
    ["title"]=>
    string(15) "泥のように"
    ["link"]=>
    string(23) "http://blog.tojiru.net/"
    ["description"]=>
    string(57) "プログラミングのネタを思いつくままに。"
    ["items"]=>
    object(SimpleXMLElement)#18 (0) {
    }
  } 
  ["item"]=>
  array(15) {
    [0]=>
    object(SimpleXMLElement)#3 (3) {
      ["link"]=>
      string(45) "http://blog.tojiru.net/article/261749730.html"
      ["title"]=>
      string(69) "PHPでXMLを生成するためのライブラリを公開しました"
      ["description"]=>
      string(81) "DOMのラッパーを書きました。XML生成コードを短くできます。"
    }
(...)

…表示される情報が少ないように思いませんか? 例に示した「http://blog.tojiru.net/index.rdf」はこのブログのRSS1.0フィードのURLです。私は記事全文をRSSに吐いているため、記事の本文が含まれているはずですが、表示されていません。

これは、SimpleXMLが名前空間の違う要素を区別する仕様だからです。記事本文はxmlns:content="http://purl.org/rss/1.0/modules/content/"という名前空間になっていて、他の要素と違うため、取れないのです。

SimpleXMLElement->children()で名前空間を明示すれば取れるのですが、正直ちょっと面倒くさいですね。

続きを読む
タグ:PHP XML_Builder
posted by Hiraku at 00:28 | Comment(0) | TrackBack(0) | PHP | このブログの読者になる | 更新情報をチェックする

2012年04月01日

PHPでXMLを生成するためのライブラリを公開しました

XMLを生成するためのライブラリ「XML_Builder」をOpenpearにて公開しています。1月ごろにはもう公開していたのですが、ブログに書いていなかったので、簡単な紹介を書いてみたいと思います。(4/1ですがエイプリルフール関係ない記事です)

github pagesに簡単なドキュメントも置いています。

php-XML_Builder document
https://github.com/hirak/php-XML_Builder

インストール

PHP PEAR形式のライブラリです。OpenpearなのでPEARコマンドで簡単です。

% pear channel-discover openpear.org
% pear install openpear/XML_Builder

肝心のXMLを出力するにはDOMやXMLWriterが必要ですが、arrayを生成するだけのモードもあるため、特に依存を設定していません。

大抵、DOMはデフォルトでインストールされていると思います。

XML_Builderの文法

XML_BuilderはXMLをとにかく短い記法で書くことができる、DOMやXMLWriterのラッパークラスです。

require_once 'XML/Builder.php';

XML_Builder::factory()
->entry(array('xmlns'=>XML_Builder::NS_ATOM))
  ->id_('http://blog.tojiru.net/')
  ->title_('泥のように')
  ->updated_(date('c'))
  ->link_(array('href'=>'http://blog.tojiru.net'))
->_
->_echo();

XML_Builder::factory()から始めて、そのままメソッドチェーンで要素を作っていき、最後に_echo()で即その場で書きだします。

ソースを見て、何となく出力されるXMLが想像できるのではないでしょうか?

<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
  <id>http://blog.tojiru.net/</id>
  <title>泥のように</title>
  <updated>2011-01-01T00:00:00+09:00</updated>
  <link href="http://blog.tojiru.net"/>
</entry>

詳しい文法はドキュメントを読んでください。

ちなみに、制御構造(条件分岐や繰り返し)は_do()メソッドでコールバック関数を即座に実行する機能があるので、無名関数で渡すのが簡単です。関数は現在のコンテキストのビルダーオブジェクトを引数に受け取ります。

XML_Builder::factory()
->root
  ->_do(function($builder){
    for ($i=0; $i<5; $i++) {
      $builder->num_($i);
    }
  })
->_
->_echo();

PHP5.2の場合は無名関数が使えないので、メソッドチェーンを分割しましょう。xmlPause()で現在のコンテキストを書き出すことができます。

XML_Builder::factory()
->root
  ->xmlPause($builder); //一度チェーンを切る
  for ($i=0; $i<5; $i++) {
    $builder->num_($i);
  }
  $builder              //チェーンを再開
->_
->_echo();
続きを読む
タグ:PHP xml XML_Builder
posted by Hiraku at 23:52 | Comment(0) | TrackBack(0) | XML_Builder | このブログの読者になる | 更新情報をチェックする