流れるようなスタイル with With (VB.NET)

おはようございます!久しぶりに忙しくなかったので夕方に仮眠のつもりで寝て起きたら11時間くらい経過していたので早起きできました。…おはようございます…。
今日は「流れるようなインターフェース」のサンプルをじーっと見ているとVB.NETのWith文を使ったコードに見えてくるというお話です。

「流れるようなインターフェース」にある流れるようなスタイルのサンプルはこんな感じでした。

  private void makeFluent(Customer customer) {
       customer.newOrder()
               .with(6, "TAL")
               .with(5, "HPK").skippable()
               .with(3, "LGV")
               .priorityRush();
   }

これをVB.NETで書いてみましょう。

    Private Sub MakeFluent(ByVal customer As Customer)
        With customer.NewOrder
            .With(6, "TAL")
            .With(5, "HPK") : .Skippable()
            .With(3, "LGV")
            .PriorityRush()
        End With
    End Sub

似ていませんか?
顧客と注文の定義はこうできます。With()やSkippable()などでCustomerを返す必要はありません(= FunctionではなくSubにできます)。

Public Class Order
    Public Sub [With](ByVal amount As Integer, ByVal productName As String)
        ' ... 注文明細を追加するコード ...
    End Sub
    Public Sub Skippable()
        ' ... 呼ばれた時点での最後の注文明細をスキップするコード ...
    End Sub
    Public Sub PriorityRush()
        ' ... 注文の状態を至急にするコード ...
    End Sub
End Class

Public Class Customer
    Public Function NewOrder() As Order
        Dim o As New Order
        ' ... 何かするコード ...
        Return o
    End Function
End Class

なので良い点があるとすれば流れるようなインターフェースを定義しなくても(コマンド・クエリ分離の原則に則しつつ)流れるようなスタイルで書けるところでしょうか。流れるようなスタイルと流れるようでないスタイルのどちらでも理解できるメソッド名を考えるのがつらそうですが…。

Dictionaryの初期化 + With文ならこうですね(2個目のdictが省略できればもっとナイスなんですけれど…。VB 10だとC#のコレクション初期化子に似た形で初期化ができるそうです)。

        Dim dict As New Dictionary(Of String, Integer) : With dict
            .Add("boo", "nobuyo")
            .Add("foo", "katsue")
            .Add("woo", "tetsuko")
        End With

JavaScriptにもwith文がありますが、JavaScriptのwith文ではメソッド(プロパティ)を使うときにドットがいらないので、ぱっと見で誰(withで指定したオブジェクトなのかその上のスコープなのか)の持ち物を使っているのかがわかりづらそうです。

今日は流れるようなスタイルを実現するには言語レベルとかメタレベルで対応がされている方がうれしいかもしれませんというお話…ではありませんでした。単に流れるようなスタイルとVB.NETのWith文を使ったコードが似ているということでしたね!
それではー。

isset()と!is_null()の値は常に等しいか

こんばんは!今日はPHPのisset()と!is_null()(is_null()を反転したもの)に同じ値を与えたとき*1、返されるbool値は常に等しいかどうかという小さいお話です。小さい話なのに長くなってしまいましたが…。物分かりが悪く、物忘れが激しく、読解力がない、そんな自分に説明するとなればこれでもかというくらい丁寧に書くしかないですよね。はい。ありがとう。面目ねぇ。そんな感じでやっていきたいと思いますが、まずisset()とis_null()の返す値と条件についておさらいしましょう。

isset() ([http
//www.php.net/manual/ja/function.isset.php:title]):変数がセットされており、それが NULL でないならTRUEを返す
is_null() ([http
//www.php.net/manual/ja/function.is-null.php:title]):指定した変数がNULLならTRUEを返す

こんな感じでした。これに加えて未初期化変数など(上の引用文に言葉を合わせるとセットされていない変数)は暗黙に変換される状況でなければNULLを返すようになっているので(PHP: 基本的な事 - ManualPHP: 配列 - Manual #構文など)、isset()と!is_null()の値は等しくなるように思えました。

<?php
// 未初期化変数の値を調べる場合
var_dump(isset($foo));
var_dump(!is_null(@$foo)); // 一応E_NOTICEが出ないようにエラー制御演算子(@)を付けています

// array内の未定義の要素の値を調べる場合
$bar = array();
var_dump(isset($bar["x"]));
var_dump(!is_null(@$bar["x"]));

この結果は次のようになります。

bool(false)
bool(false)
bool(false)
bool(false)

同じですね。

さて、本題の「isset()と!is_null()に同じ値を与えたとき、返されるbool値は常に等しいか」についてですが、結論から書くと違いました(常に等しければ書き留める意味がないのですけれども!)。異なる値になるのは次のような場合がありました。

  • 文字列に範囲外の配列アクセスをした場合
  • __get()を意図的に定義したクラスのオブジェクトのアクセス不能プロパティ*2を読んだ場合
  • __isset()を意図的に定義したクラスのオブジェクトのアクセス不能プロパティを読んだ場合

順番に確認しましょう。

文字列に範囲外の配列アクセスをした場合

マニュアルのisset()のUser Contributed Notesを見て知ったのですが、これは次のような場合です。

<?php
$s = 'foo';
var_dump(isset($s[9]));
var_dump(!is_null(@$s[9]));

PHPでは(PHPでも)文字列に配列のようにアクセスすることができ、指定した数値が文字列の範囲内の値であれば対応する文字(1byte)が返されます(PHP: 文字列 - Manual #文字列への文字単位のアクセスと修正)。
上の例は"foo"という長さ3の文字列に対して9という範囲外の値で配列アクセスを行っている式にisset()と!is_null()をかけて比べているものですが、結果は次のようになります。

bool(false)
bool(true)

異なる値になりました。これは文字列への範囲外の配列アクセスがNULLではなく空文字を返すからです(意図はよくわかりませんが…)。

__get()を意図的に定義したクラスのオブジェクトのアクセス不能プロパティを読んだ場合

これは次のような場合です。

<?php
class A { } // 通常のクラス
class B // __get()を定義してNULL以外が返るようにしたクラス
{
  public function __get($name) { return ""; }
}

$a = new A();
echo '-- A' . PHP_EOL;
var_dump(isset($a->foo));
var_dump(!is_null(@$a->foo));

$b = new B();
echo '-- B' . PHP_EOL;
var_dump(isset($b->foo));
var_dump(!is_null(@$b->foo));

文字列の例を踏まえると、未定義と判定されつつNULL以外の値が返る場面を作ればisset()と!is_null()は異なる値になるはずですね。
この結果は次のようになります。

-- A
bool(false)
bool(false)
-- B
bool(false)
bool(true)

期待通り異なる値になりました。

__isset()を意図的に定義したクラスのオブジェクトのアクセス不能プロパティを読んだ場合

これは次のような場合です。

<?php
class A { } // 通常のクラス
class B // __isset()を定義して常にTRUEが返るようにしたクラス
{
  public function __isset($name) { return true; }
}

$a = new A();
echo '-- A' . PHP_EOL;
var_dump(isset($a->foo));
var_dump(!is_null(@$a->foo));

$b = new B();
echo '-- B' . PHP_EOL;
var_dump(isset($b->foo));
var_dump(!is_null(@$b->foo));

そういえば__isset()…と思いisset()が常にTRUEを返すような__isset()を定義してみました。文章がややこしいな。この結果は次のようになります。

-- A
bool(false)
bool(false)
-- B
bool(true)
bool(false)

異なる値に…当然と言えば当然ですね。
そういえばマニュアルには__isset() は、 isset() あるいは empty() をアクセス不能プロパティに対して実行したときに起動しますとありますが、そうすると__isset()がisset()によって呼ばれたかempty()によって呼ばれたかをどうやって__isset()内で区別するのでしょうか…。

まとめ

変数などが未定義であるときにNULLが返されなければ当然isset()と!is_null()は異なるわけですが、そういう場面がいくつか出来るということがわかりました。isset()をNULLチェックに使うのは少し危うい場面があるので考えて使いましょうということですね。
あれ、時間が…。それではー。

*1:!is_null()に値を与えるという書き方は変ですね

*2:アクセス不能プロパティの意味はPHP: オーバーロード - Manualに準じます

URIの一般的構文の正規化

こんばんは!そういえば日曜日に西本願寺に行ってきたんです。お寺っていいですよね。匂いとか。お金があったら寺買収したいです。

今日はURIの一般的構文の正規化のお話というかメモです。そういえばmailto:のtoは正規化せずに、どこかでtoのリストを持つようにして比較に使うようにすれば良いかもしれません。domainが同じでdisplay-nameが違う場合は…同値?

パーセントエンコーディング

  • パーセントエンコーディング内のa〜fは大文字に変換する (RFC 3986 section 6.2.2.1)
    • example://a/b/c/%7bfoo%7dexample://a/b/c/%7Bfoo%7D
  • 非予約文字(unreserved)がパーセントエンコーディングされている場合はそれをデコードする (RFC 3986 section 6.2.2.2)
    • example://a/b/%63/example://a/b/c/

scheme

  • 小文字に変換する (6.2.2.1)
    • HTTP://example.com/http://example.com/

authority

  • authority(host)がschemeでの規定値ならauthority(host)を削除する。userinfoやportが空でないときにhostがschemeの規定値ならhostは削除しない (RFC 3986 section 6.2.3)
    • file://localhost/foofile:///foo (file:のhostの規定値の定義はRFC 1738 3.10)
    • file://foo@localhost/localhostを削除しない
userinfo
  • 空のとき、schemeの仕様にそう定義されていなければ区切り子("@")を削除しない (RFC 3986 section 6.2.3)
    • http://@example.com/ は"@"を削除しない
host
  • 小文字に変換する
    • http://EXAMPLE.com/http://example.com/
port
  • portが空かそのschemeでの規定値ならportの区切り子(":")とportを削除する (RFC 3986 section 6.2.3)
    • http://example.com:80/http://example.com/ (http:のportの規定値の定義はRFC 2616 section 3.2.2)
    • http://example.com:/http://example.com/

path

  • ドットセグメント(パスセグメントで"."や".."に完全に一致するもの)は削除する (RFC 3986 section 6.2.2.3)
    • http://example.com/a/./b/../http://example.com/a/
  • authorityがありpathが空ならpathを "/" にする (RFC 3986 section 6.2.3)
    • http://example.comhttp://example.com/

query

  • 空のとき、schemeの仕様にそう定義されていなければ区切り子("?")を削除しない (RFC 3986 section 6.2.3)
    • http://example.com/? は"?"を削除しない

fragment

  • 空のとき、schemeの仕様に関わらず区切り子("#")を削除しない (RFC 3986 section 6.2.3)
    • http://example.com/# は"#"を削除しない

寺は買収できるんですかね…。買収できなかったら建立ですね。

PHPでURIを扱うものをつくろうの会

こんばんは!こっちでは最近寒かったり暑かったりしますが風邪など引いてないですか?最近本屋でどうぶつしょうぎを見つけて衝動買いしてしまったんですが、考えたらやってくれる相手が近くにいなくて悲しい思いをしています…。子供さんがいたら買ってあげるといいかもしれません。

あと最近PHPをやることになっているので、NetBeans 6.8 beta + PHPUnit 3.4.2の試用とPHPURIの勉強をかねてPHPURIを扱うものをテスト実装しようとしています…というのが今回のお話です。PHPの関数名がフリーダムでやりきれない思いになったりarrayとかempty()とかの挙動に引っかかったりしながらこんな感じのことを考えてつくっています。

  • RFC 3986ベースでできるだけRFCに沿って実装する
    • 相対参照(relative reference)はURIとして表現しないとか (RFC 3986 section 1.2.3)
    • mailto:addr1%2C%20addr2 と mailto:?to=addr1%2C%20addr2 と mailto:addr1?to=addr2 を比較して等しくなるようにするとか (RFC 2368 section 2)
    • RFC 3986 + scheme固有でできるだけちゃんと正規化するとか (比較に必要なので)
  • 一般的構文, http:, https:, ftp:, file:, mailto:, data: あたりに対応したい
  • RubyURIの生成手順*1は良いと思うので生成手順とクラスの構成はRubyURIに近い形にする
  • URI生成時にデフォルトでURIを正規化すると非正規形のURIが表現できないのでデフォルトで正規化しない (.NET FrameworkのSystem.Uriはデフォルトで正規化するみたいです)

今のところhost(IP-literalとIPv4address)以外の解析とチェック、一般的構文の正規化(ポートの正規化とかドットセグメントの削除とか)ができるようになっているところです。RFC 3986は親切なことに実装例が書いてあるのでありがたいです。

IRIをどうするかはまだ決まっていません。mailto:の正規化はできないかもしれません(そもそも正規形が何かについて書いていなさそうなのと、mailbox内のdomainの小文字化が出来そうかわからないので…)。

mailto:とdata:の謎のurlcとかurlcharとかは今のところ無視してRFC 3986の一般的構文として解析してからscheme固有の制限のチェックなどをすることにしています。ただurlcとかurlcharがRFC 2396のuricなら、queryの区切り子("?")がuricに含まれていること、RFC 2396のopaque_partの部分がRFC 3986だとpath-rootlessとqueryに収束する(と思う)関係で、例えばdata:text/plain,foo?barを解析するとpathが"text/plain,foo"でqueryが"bar"みたいに分かれてしまうのが何だか面白くないです…。

あとPHPのどのバージョンに合わせるかなんですが、遅延静的束縛を使わないといけない形になっているので今は5.3以上になってしまっています。割り切って完全に5.3以上にするならcreate_function()は面倒なので無名関数を使ったりしたいです。

ちょっと文章の整理ができていないですがもう夜遅くなってきたのでこのお話の続きは今度にして寝ます。ではー。

*1:Kernel#URI()とかURI.parse()からschemeを見てURI::GenericかURIモジュールの@@schemesに登録されたURI::Genericのサブクラスのインスタンスを生成する形

「NodeListがliveなものを返すってDOM3 Coreのバグじゃね?」と言われているのはなんで?

querySelectorAllがliveじゃないNodeList返すのはなんで? - vantguarde - web:gからの話なのですが、気になったので調べています。

今のところ「NodeListがliveなものを返すってDOM3 Coreのバグじゃね?」と言われている理由の予想としてはこんな感じかな、と思います。

  • liveで嬉しいことがあまりないこと((liveで嬉しいことをどなたかいろいろ教えていただけると嬉しいです。自分としては「過去の慣習」、「getElementsByTagName()等の呼び出しが一回ですむ」、「childNodesを親Nodeから切り離しても切り離さなくても同じように扱える」くらいのものしか思いつきませんでした))
  • 実装にかかる時間(実装者の問題)
  • 処理速度(実装者、スクリプト作成者、ユーザーの問題)
  • 繰り返し構文+インデックスアクセスでのDOM木への追加または削除のケース等で混乱を招きやすい(スクリプト作成者の問題)
  • 仕様に使いにくい(策定者の問題)

仕様に使いにくいというのは、今のNodeListは「DOM木の変更によって自動更新されるNodeの文書順リスト」*1なので単に「Nodeの(文書順)リスト」として使いたい仕様の場合(例えば高度な検索条件を指定して結果をリストとして返すような仕様の場合、主に軽量機器向けの仕様の場合)、NodeListを敬遠せざるを得ないということです。もし単に「Nodeの(文書順)リスト」というだけであればDOM XPathはもう少し不細工でないように出来たんじゃないかと思います(Element Traversalなんかも?)。

Selectors APIquerySelectorAll()のNodeListをliveにする場合は、擬似クラス(特に:hover:active)のことを考えると最悪「DOM木の変更とユーザーアクション(ポインティングデバイスの移動等)によって自動更新されるNodeの文書順リスト」になってしまって、スクリプト作成者にさらなる混乱を与えることになるかもしれません。また、CSS3セレクタXPathには及びませんが、さまざまなセレクタやグループ化によって十分に複雑な検索が可能ですから速度的な面も心配です。

このままだと今後NodeListを嫌った高レイヤーの仕様が軒並み不細工になっていくおそれがあるので、今後のことを考えるとNodeList自体をデフォルトをstaticにしてオプションでliveにした方が良いような感じがします。

メモ*2

バグ発言関連
Element Traversal, SVG Tiny 1.2

*1:http://www.w3.org/TR/DOM-Level-3-Core/core.html#td-live等。あれ、ordered listsとはあるけどdocument order云々とは書いてないんですね。文書順じゃない順序つきのNodeListって何かあったかな…

*2:偏向有

SGML/XML処理命令, PHP short_open_tag on / off

SGML

処理指令    = pio, システムデータ, pic        ―(44)
システムデータ = 文字データ                 ―(45)
SGMLsec7_0.html, Latest updated 1998.01.19
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<title></title><p></p>
<? /* php code */ ?>

XML

[16]   	PI	   ::=   	'<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
[17]   	PITarget	   ::=   	Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
Extensible Markup Language (XML) 1.0 (Fifth Edition)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><title></title></head><body><p></p>
<?php /* php code */ ?>
</body></html>