2008年06月10日

Scala と specs の簡単なサンプル



暫くぶりに投稿します。
近頃は、時間があればScalaを勉強しています。
ScalaはJavaにはない興味深い機能が多くあり、それらの内の幾つかを コードを見ながら、ここで、解説していきたいと思います。
また勉強の過程で知ったspecsという検証用のフレームワークがなかなか おもしろかったので、そちらも紹介したいと思います。

Scala

Scalaは特徴の多いプログラミング言語です。
関数を変数として保持できたり、クロージャ、多重継承やパターンマッチ、 Erlangのような並列処理機構などなど数多くの機能を備えています。
学習の敷居はそれなりに高い(私は苦戦中...)のですが、言語の表現力の高さや、 パフォーマンス、 Javaライブラリを利用可能など、魅力に富んでいます。
個人的には構文解析のパーサが気になります。(TODO:後で勉強。)

リンク

specs

specsはScalaでJUnitのようにテストを実施するためのフレームワークです。
specsは仕様を書くような感覚で検証コードを書くことをその設計思想としている ようです。(BDD: Behavior Driven Development )
人に優しい、プログラムの読みやすさや簡潔さにこだわりを感じるフレームワークです。

リンク

Scala - 電話番号っぽい?

Scalaの構文を勉強しながら、ちょっとしたコードを書きました。
以下は、文字列が電話番号っぽいかを判定します。
object PhoneNumber { val phoneNumPtn = """^(\d{2,4}+)\-?(\d{2,4}+)\-?(\d{2,4}+)$""".r // 電話番号ぽっい番号の場合はtrueを返す def isValid(phoneNumber:String):Boolean = { phoneNumber match { case phoneNumPtn(s1:String, s2:String, s3:String) => { println( "valid : " + s1 + s2 + s3 ); return true; } case _ => return false } } }
※ ここでは、Scalaのバージョン 2.7.1.final を使用しています。


コードの解説 / バラバラと

object PhoneNumber { ... }
objectで定義するとPhoneNumberはシングルトンとしてインスタンス化されます。
class PhoneNumber も別途定義できます。


val phoneNumPtn = """^(\d{2,4}+)\-?(\d{2,4}+)\-?(\d{2,4}+)$""".r
これは正規表現パターンを定義します。
valは変更不可の値を定義します。Javaのfinalのようなものです。 ( 変更可能な値は var で定義します。 )

"fugafuga".rの様に文字列に.r を付与すると、文字列が正規表現として解釈され、scala.util.matching.Regex 型のインスタンスが作られます。
また、""" で文字列を囲うと、いわゆるヒアドキュメントとして扱われます。
ヒアドキュメントは、複数行に渡る文字列を定義するときなどに使用します。ここでは、\マークをエスケープせずに記述するために使用しています。


def isValid(phoneNumber:String):Boolean = { ... }
def はメソッドを定義するときに使用します。
このメソッドはString型の引数を1つ受け取ります。
末尾の :Boolean はこのメソッドが Boolean型 を返すことを意味します。


phoneNumber match { case phoneNumPtn(s1:String, s2:String, s3:String) => { println( "valid : " + s1 + s2 + s3 ); return true; } case _ => return false }
match { case xxx => { ... } case yyy => { ... } } はパターンマッチングの構文です。case の条件を上から順に評価し、条件にマッチした場合、=> の右辺のブロックが実行されます。

phoneNumPtn(s1:String, s2:String, s3:String) の部分では、phoneNumPtnで定義される正規表現にマッチする場合、正規表現の()で囲>われた値に相当する文字列をs1,s2,s3にセットします。

この例で文字列 11-22-33 でマッチングさせると s1="11" s2="22" s3="33" になります。
case _ は必ずマッチします。

specsで検証

specsを使って先述のコードを検証するコードを用意しました。
import org.specs._ object PhoneNumberSpec extends Specification { "正しい電話番号である" in { PhoneNumber.isValid("0120-000-000") mustBe true PhoneNumber.isValid("03-0000-0000") mustBe true PhoneNumber.isValid("090-0000-0000") mustBe true PhoneNumber.isValid("110") mustBe true // failure } }
※ ここでは、specsのバージョン 1.2.9 を使用しています。

コードにある通り「正しい電話番号である」ことを検証します。
specsは簡潔に記述すること、何を行うのかを明瞭におくことを易しくしてくれます。

検証処理の流れ


1. 暗黙的型変換 - implicit conversion

実際に処理がどのように呼び出されるかを確認します。
上記のコードでは主な検証処理を以下の様な形式で実行しています。
"文字列" in { ..処理.. }
このコードには若干トリックが含まれています。タネは、継承元のクラスにあります。
上記のクラスの親の一つにSpecificationStructureクラスがあり、そこに以下のメソッドが定義がされています。
implicit def forExample(desc: String): Example = { ... }
このコードによって、文字列は暗黙的にExample型のインスタンスに変換されます。
in メソッドはString型に定義はなく、Example型に定義されているため、Exapmple型へ変換されるのです。
変換処理が上述のforExampleメソッドです。(メソッドの名前は何でも構いません。引数と返却値の型によって呼び出し先が決定されます。)
これは、Scalaの implicit conversion (暗黙の型変換) と呼ばれる機能です。


2. 省略記法、名前渡し

次に in の部分ですがこれは、in というメソッドを呼び出しています。
Scalaでは、メソッド呼び出し時にドットや引数の括弧などを省略して記述することができます。
元のコードの省略記法を利用せずに書き直すと、以下の様になります。
forExample("正しい電話番号である").in( { PhoneNumber.isValid("0120-000-000").mustBe(true) PhoneNumber.isValid("03-0000-0000").mustBe(true) PhoneNumber.isValid("090-0000-0000").mustBe(true) PhoneNumber.isValid("110").mustBe(true) // failure } )
(どちらの書き方が良いかは好みにもよりますね。)

inメソッドは、Any型の値を1つ「名前渡し(call-by-name)」で受け取って、Example型を返すメソッドです。
メソッドはExampleクラスに定義されています。
def in (test: => Any): Example = { .... }
引数の型を 「 => Any 」 の様に => を付与して宣言することで、名前渡しにすることができます。
名前渡しの場合、引数が参照される度に、値が評価されます。
今回の例では、{}で囲まれたブロックを渡しており、ブロックの中身が in メソッドの中で評価されます。
結果として、ブロックの中味が実行されて、検証の処理が行われます。


3. 再び implicit conversion

ブロックの中では mustBe メソッドを呼び出しています。
これも implicit conversionによって、実際にはAssertクラスに変換されて、mustBe メソッドが 呼び出されます。
このimplicit conversionは、Specificationの親クラスの一つである、AsserFactoryに定義されています。
implicit def theValue[A](value: =>A) = { addAssertion new Assert[A](value) }
この処理を明示的にして書き直すとこのようになります。
forExample("正しい電話番号である").in( { theValue( PhoneNumber.isValid("0120-000-000") ).mustBe(true) theValue( PhoneNumber.isValid("03-0000-0000") ).mustBe(true) theValue( PhoneNumber.isValid("090-0000-0000") ).mustBe(true) theValue( PhoneNumber.isValid("110") ).mustBe(true) // failure } )
明示的にすることで、処理の流れは分りやすくなりましたが、 コードの意図、即ち何をしようとしているのかが若干分りづらくなったように感じます。 Scalaでコードを書くときには、この辺りのトレードオフを意識しておくとよいかもしれません。

実行結果

> fsc PhoneNumber*.scala > scala PhoneNumberSpec Specification "PhoneNumberSpec" specifies valid : 0120000000 valid : 0300000000 valid : 09000000000 x 正しい電話番号である 'false' is not the same as 'true' (PhoneNumberSpec.scala:9) Total for specification "PhoneNumberSpec": Finished in 0 second, 113 ms 1 example, 4 assertions, 1 failure, 0 error

おわりに

まだ深くは見ていませんが、specsのAPIは良く考えられていて、実用性のある機能が 利用しやすい形で提供されている気がします。
こういったAPIを見て、簡潔に物事を表現する手段として、 Scalaイケテルヤモ? と思っている今日この頃です。では。

2007年11月07日

プロジェクト管理サービス

最近、プロジェクト管理やタスク管理用のオンラインサービスがかなり充実してきています。
オンラインのサービスは、情報管理と共有といった目的を達成する手段として、相性の良い組み合わせですよね。
ただ、多くのサービスがあるのですが、なんとなく、しっくりくるものがない状態です。
おそらく、今後も数多くの製品・サービスが産まれ、淘汰が進んでいくのではないでしょうか。

ここでは、プロジェクト管理系のサービスで、今私が気になっているものを紹介します。



  1. Acunote
    • アジャイルソフトウェア開発の思想に基づいている。(スプリント,バックログなど)
    • タスクの進捗管理に長けていて、担当者単位でグラフ表示できる。
    • ソースコードを表示してコメントを書ける。
    • バグトラッカー(Bugzilla, Mantis, Trac)から、バグリストをインポートできる(らしい)。
    • サンプル画面
      Acunote.png
  2. Lighthouse
    • シンプルなタスク管理。
    • マイルストーンに紐付くチケット(タスク)を発行し、プロジェクトメンバの中から担当者を決める。
    • 画面構成が綺麗。(若干迷う気もするが)
    • APIが公開されている。(ドキュメント)
    • サンプル画面
      Lighthouse.png
  3. MyQuire
    • プロジェクト管理だけでなく、SNS色もある。
    • メッセージやチャットなどのコミュニケーション機能が充実。
    • 他にもカレンダーやファイルスペースなどが用意されているオールインワンな環境。
    • サンプル画面
      MyQuire.png

以上、3点です。
# いずれも、無料で試せます。

2007年09月14日

WYSIWYGエディタに夢中になったときのメモ

javascriptでHTML-WYSIWYGエディタを作ろうとしたときのメモを公開します。



1. 編集 - document.designMode

documentオブジェクトにはdesignModeというプロパティがあります。
このプロパティをonにすると、そのdocumentは編集可能になります。

iframe内のdocumentを編集可能するにはこのようにします。
var doc, iframe = document.getElementById('xxx'); // IE, Operaの場合 doc = iframe.contentWindow.document; // FF, Safariの場合 doc = iframe.contentDocument; doc.designMode = 'on';


2. 装飾 - document.execCommand()

あるボタンを押すと文字の色が変わる、などの制御を入れたい場合には、 メソッドexecCommandを使用すると便利です。

  • 太字にする
  • document.execCommand( "bold", false, null );
  • 文字の色を赤くする
  • document.execCommand( "forecolor", false, 'red' );

execCommandの第一引数には、文字列でコマンドを指定します。
コマンドによっては第三引数の、パラメータが必要になります。

コマンドの一覧は以下に記載されています。 ( operaとsafariの資料はどこにあるのでしょう? )
Firefoxは、英語ドキュメントの方が、より多くのコマンドが載っています。



3. 生成されるHTMLの違い

execCommandを使用して生成されるHTMLは、ブラウザによって違います。

この違いから、あるブラウザで生成したHTMLを(サーバ上で保持させ)、別のブラウザで 編集しようとすると、execCommandが期待通りに動いてくれないときがあります。
クロスブラウザの環境をサポートしたい場合には、この違いを吸収してあげなくてはなりません。

生成されるHTMLを、手元の環境で確認した結果をまとめてみました。 (少し見づらいですが)
Table Navigation:
commandInternet Explorer 6Firefox 2.0 (styleWithCSS=false)Firefox 2.0 (styleWithCSS=true)Safari 3.0.3 (windows版)Opera 9.23
backcolor<FONT style="BACKGROUND-COLOR: red">Hello</FONT>HelloHello<span class="Apple-style-span" style="background-color: red;">Hello</span>Hello
bold<STRONG>Hello</STRONG><b>Hello</b><span style="font-weight: bold;">Hello</span><span class="Apple-style-span" style="font-weight: bold;">Hello</span><STRONG>Hello</STRONG>
createlink<A href="http://www.google.co.jp">Hello</A><a href="http://www.google.co.jp">Hello</a><a href="http://www.google.co.jp">Hello</a><a href="http://www.google.co.jp">Hello</a><A href="http://www.google.co.jp">Hello</A>
fontname<FONT face=sans-serif>Hello</FONT><font face="sans-serif">Hello</font><span style="font-family: sans-serif;">Hello</span><span class="Apple-style-span" style="font-family: sans-serif;">Hello</span><FONT face="sans-serif">Hello</FONT>
fontsize<FONT size=2>Hello</FONT><font size="2">Hello</font><font size="2">Hello</font><span class="Apple-style-span" style="font-size: small;">Hello</span><FONT size="2">Hello</FONT>
forecolor<FONT color=red>Hello</FONT><font color="red">Hello</font><span style="color: red;">Hello</span><span class="Apple-style-span" style="color: red;">Hello</span><FONT color="#ff0000">Hello</FONT>
formatblock<H1>Hello</H1><h1>Hello</h1><h1>Hello</h1><h1>Hello<br></h1><H1>Hello</H1>
indent<BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"> <P>Hello</P></BLOCKQUOTE><blockquote>Hello</blockquote><div style="margin-left: 40px;">Hello</div><blockquote class="webkit-indent-blockquote">Hello<br></blockquote><BLOCKQUOTE>Hello</BLOCKQUOTE>
inserthorizontalrule<HR><hr size="2" width="100%"><hr style="width: 100%; height: 2px;"><hr id="undefined"></hr><HR>
insertimage<IMG src="http://www.google.co.jp/intl/ja_jp/images/logo.gif"><img src="http://www.google.co.jp/intl/ja_jp/images/logo.gif"><img src="http://www.google.co.jp/intl/ja_jp/images/logo.gif"><img src="http://www.google.co.jp/intl/ja_jp/images/logo.gif"><br><IMG src="http://www.google.co.jp/intl/ja_jp/images/logo.gif">
insertorderedlist<OL> <LI>Hello</LI></OL><ol><li>Hello</li></ol><ol><li>Hello</li></ol><ol id="undefined"><li>Hello<br></li></ol><OL><LI>Hello</LI></OL>
insertunorderedlist<UL> <LI>Hello</LI></UL><ul><li>Hello</li></ul><ul><li>Hello</li></ul><ul id="undefined"><li>Hello<br></li></ul><UL><LI>Hello</LI></UL>
italic<EM>Hello</EM><i>Hello</i><span style="font-style: italic;">Hello</span><span class="Apple-style-span" style="font-style: italic;">Hello</span><I>Hello</I>
justifycenter<P align=center>Hello</P><div align="center">Hello</div><div style="text-align: center;">Hello</div><DIV align="center">Hello</DIV>
justifyfull<P align=justify>Hello</P><div align="justify">Hello</div><div style="text-align: justify;">Hello</div><DIV align="justify">Hello</DIV>
justifyleft<P align=left>Hello</P><div align="left">Hello</div><div style="text-align: left;">Hello</div><DIV align="left">Hello</DIV>
justifyright<P align=right>Hello</P><div align="right">Hello</div><div style="text-align: right;">Hello</div><DIV align="right">Hello</DIV>
strikethrough<STRIKE>Hello</STRIKE><strike>Hello</strike><span style="text-decoration: line-through;">Hello</span><span class="Apple-style-span" style="text-decoration: line-through;">Hello</span><STRIKE>Hello</STRIKE>
subscript<SUB>Hello</SUB><sub>Hello</sub><sub>Hello</sub><span class="Apple-style-span" style="vertical-align: sub;">Hello</span><SUB>Hello</SUB>
superscript<SUP>Hello</SUP><sup>Hello</sup><sup>Hello</sup><span class="Apple-style-span" style="vertical-align: super;">Hello</span><SUP>Hello</SUP>
underline<U>Hello</U><u>Hello</u><span style="text-decoration: underline;">Hello</span><span class="Apple-style-span" style="text-decoration: underline;">Hello</span><U>Hello</U>

Firefoxでは、 execCommand で styleWithCSS を false にしてあげると、生成されるHTMLが、IE,Operaのものにより近くなります。
デフォルトはtrueです。



4. 改行の違い

IE6で改行を入力すると、なぜか2行分改行されているように見えます。
これは、改行入力時には、<br>タグではなく、<p></p>タグが新たに挿入されるためです。


ちなみに、<p>タグ内での改行では<p>が挿入されますし、<div>タグ内の改行なら<div>、<h1>タグ内なら<h1>といった具合に、カーソル位置の親のブロックエレメントタグが挿入されるようです。
また、Shiftキーを押しながらEnterキーを押すと、<br>タグを挿入できます。

幾つか対策方法を探してみました。

  • スタイルシートで、pタグのmargin-top と margin-bottom に 0px を指定する。

    これで見た目上の問題はなくなります。

    p { margin-top: 0px, margin-left:0px }
  • 独自に<br>タグを挿入し、イベント伝播を停止する。

    どうしても<br>タグを使いたい場合には、この方法が良さそうです。
    下記のfunctionで、keydownイベントをリスンしておくようにします。

    // IE Only function keydownHandler(e){ // Handle Enter Key if( e.keyCode == 13 && !e.shiftKey){ var range = document.selection.createRange(); range.pasteHTML("<br>"); e.returnValue = false; e.cancelBubble = true; } }



5. Range

最後に、Range (TextRange)オブジェクトを紹介します。 execCommandに定義されていない処理を行いたい場合など、何かと便利なオブジェクトです。


document上の現在のカーソル位置、もしくは、選択されている領域の、Rangeを取得します。

// IE var range = document.selection.createRange(); // IE以外 // 引数に0を指定しているので、先頭のRangeオブジェクトが取得されます。 var range = document.getSelection().getRangeAt(0); // IE以外でiframeから取得する場合 var range = iframe.contentWindow.getSelection().getRangeAt(0);


このRange(TextRange)オブジェクトを使うと色々なことができます。
ここでは、クロスブラウザを意識した操作を幾つか紹介します。


  • rangeの中に含まれるエレメントに対して共通の親エレメントを取得します。
    // IE var parent = range.parentElement(); // IE以外 var parent = range.commonAncestorContainer;
  • range同士の位置関係を比較します。
    // IE // targetRange が baserangeより右 (baseRange.compareEndPoints('StartToStart', targetRange) <=0) // targetRange が baserangeより左 (baseRange.compareEndPoints('EndToEnd', targetRange) >= 0) // IE以外 // targetRange が baserangeより右 (baseRange.compareBoundaryPoints(Range.START_TO_START, targetRange) <=0) // targetRange が baserangeより左 (baseRange.compareBoundaryPoints(Range.END_TO_END, targetRange) >=0)
  • rangeの始点、終点を、特定のエレメントの位置に移動させます。
    // IE var startElm = document.getElementById('elm1'); var endElm = document.getElementById('elm2'); var dstRange = document.body.createTextRange(); dstRange.moveToElementText(startElm); range.setEndPoint( 'StartToStart', dstRange); dstRange.moveToElementText(endElm); range.setEndPoint( 'EndToEnd', dstRange ); // IE以外 var startElm = document.getElementById('elm1'); var endElm = document.getElementById('elm2'); range.setStartBefore(startElm); range.setEndAfter(endElm);
  • rangeからHTML文字列を取得します。
    // IE var html = range.htmlText; // IE以外 var df = range.cloneContents(); var doc = range.startContainer.ownerDocument; var div = doc.createElement('div'); div.appendChild(df); var html = div.innerHTML;
  • rangeの位置にHTML文字列を挿入します。
    // IE var html = '<p>Hello</p>'; range.pasteHTML( html ); // IE以外 var html = '<p>Hello</p>'; document.execCommand('inserthtml', false, html); // これrange関係ないけど =)

とりあえずここまで。

最後に。
間違ってたらごめんなさい。

2007年07月21日

Javascript 安全なコンストラクタ

Javascriptのthisについて。

thisは、呼び出し元のオブジェクトを参照します。

オブジェクトの外ではどうか。

thisは厳密にはコンテキストオブジェクトを参照します。
(詳しい解説はthis Operatorを参照してください) オブジェクトのメソッド以外でthisを参照すると、グローバルオブジェクトであるwindowオブジェクトを参照できます。

コンストラクタのthis

new演算子を使用すると、生成されるインスタンスがthisになります。
thisに値をセットしておけば、生成後のインスタンスから値を参照することができます。

何が問題か?

あ゛new忘れた
この場合、値を参照するとエラーになります。
しかも、値は関数内のコンテキストオブジェクトにセットされてしまいます。

安全なコンストラクタ

関数が定義されているオブジェクトとthisが同じであれば、newするようにします。
これで安心してnewの存在を忘れられる・・・

2007年07月11日

CodePress - シンタックスハイライト

掲載するソースコードを、シンタックスハイライトして表示するために、CodePressを用いてみました。

使い方

スクリプトをロードして、textareaのclassにcodepressを追加します。 textareaのclaasにはさらに、使用する言語を指定します。 javascriptやhtml, java, ruby等に対応しているようです。
<script src="/codepress/codepress.js" type="text/javascript"></script>
 ....
<textarea id="ThisIdmustBeUnique" class="codepress javascript">
   javascriptのコード
</textarea>

試行

以下のJavascriptコードで試してみます。
// Syntax Highlight Sample...
var f =function(){
  alert('test');
};
f();
CodePress適用

見た目も綺麗でいい感じ。

動作 (v.0.9.5)

適用対象の位置に、iframeを追加し、その中でcodepress.htmlを読み込んでいます。 codepress.htmlでは、必要なスクリプトとスタイルシートを読み込み、さらに適用対象の中身をロードして表示しています。

Movable Type での使用

CodePressでは、(textareaの)idが必須であり、他のエレメントのidと重複すると、Javascriptエラーがおきます。 (この辺りはブラウザによっても挙動が変わるようですが…)

Movable Type で使用する場合、検索やアーカイブなどの画面で、他のブログ、エントリが表示されるので、idがユニークになるように注意しておく必要があります。

2007年07月03日

背景画像をランダムに変更する

個人的に最近ホットなjavascriptライブラリ。jQuery。
昨日、新しいバージョンが出たのでお試しも兼ねて、このサイトに導入してみました。

画面上部の画像はランダムに移り変わります。

この写真は三浦海岸の夕暮れ時です。
限られた枠から見る自然というのも、以外と面白い。