最近周りでScalaが盛り上がっているのでつられて勉強中。
パターンマッチングでどのようなものが使えるのかサンプル込みでまとめられた資料が 見あたらなかったので、仕様書を見つつメモ。 (まだ理解があやふやなので内容は怪しいですが。。)
var x = 0 var _ = 0
var x: Int = 0
8.2 Type Patternsに
The bottom types scala.Nothing and scala.Null cannot be used as type patterns, because they would match nothing in any case.
とあるように、Nothing、Nullは指定できない。
scala> null match { case x: Null => 0 }
<console>:6: error: this type cannot be used in a type pattern
でも、Nullは定義文としては使える。
scala> var x: Null = null x: Null = null
他にもこのパターンはいろいろな書き方があるっぽいけど、 今は手に余るのでとりあえずパス。
0 match { case 0 => 1 }
定義文とmatch式で使えるパターンはほぼ同じようなので以下のようなことも可能。
var 0 = 0
2つの引数を受け取り値が等しいかどうかを判定する関数fを考える。
def f(x: Int, y: Int): Boolean = x match {
case y => true
case _ => false
}
上記のように書くと、「case y」のyがvariable patternと見なされるため 「case _」がerror: unreachable codeとなりコンパイルすら通らない。
これを回避するのがこのパターン。
def f(x: Int, y: Int): Boolean = x match {
case `y` => true
case _ => false
}
def f(x: Int, Y: Int): Boolean = x match {
case Y => true
case _ => false
}
case classによるマッチングのこと。
scala> var (a, b) = (0, 1) a: Int = 0 b: Int = 1
unapply、unapplySeqを定義したオブジェクトを利用するマッチングのこと。 文法的にはConstructor Patternsと同じで以下のような感じ。
object A {
def unapply(x: Int) = Some(x)
}
0 match {
case A(0) => 0
case _ => 1
}
// => 0
1 match {
case A(0) => 0
case _ => 1
}
// => 1
case classは「自動でapply、unapplyが定義されるクラス」と説明されることが多いが、 一応仕様としては別物のよう。
簡単に言えば、Constructor PatternやExtractor Patternで@(Haskellのas-patternと同じ)や_*が使えるということかと。
scala> var List(a, b@_*) = List(1,2) a: Int = 1 b: Seq[Int] = List(2)
上記の中置記法版。
scala> var a List b = List(1,2) a: Int = 1 b: Int = 2 scala> var a List (b, c) = List(1,2,3) a: Int = 1 b: Int = 2 c: Int = 3
この場合、_*は使えない。
scala> var a List (b, _*) = List(1,2,3) <console>:1: error: illegal start of simple pattern
1 match { case 1 | 2 => 3 }
ちなみに「1 | _」は出来るけど、 「1 | x」はNG。
scala> var <t>{a}</t> = <t>0</t>
a: scala.xml.Node = 0
Scala 2.0以降は無くなった、とのこと。 ほかにもちょこちょこと書いてあるんだけど、具体的にどういうことなのかよく分からず。
これも、いまいちよく分からない。。
積年の課題であったブラウザの乗り換えをやっと行うことが出来たのでそのまとめ。
基本的な部分は最近の定番っぽい次の拡張を入れることで問題なく移行できた。
割り当てのためにコードを書かないといけなかったのは次のもの。
Close All Tabs
gBrowser.removeAllTabsBut(gBrowser.addTab("about:blank"));
Close Other Tabs
gBrowser.removeAllTabsBut(gBrowser.mCurrentTab);
Focus Content
gBrowser.focus(); _content.focus();
Open NewsFox
openNewsfox(false);
Find [Edit] Toggle Ver.
if ("isFindBarVisible" in gFindBar)
gFindBar.isFindBarVisible() ? gFindBar.closeFindBar() : gFindBar.onFindCmd();
else
gFindBar.hidden ? gFindBar.onFindCommand() : gFindBar.close();
Lock Tab
gBrowser.lockTab(gBrowser.mCurrentTab);
Allow Page To Run Script
noscriptOverlay.allowPage()
Open Or Search Selection
userstyles.orgより、 まず全体的に見た目をコンパクトにするものを。
これに加え、タブ周りの余白も削るスタイルを追加。
@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);
tab {
margin-top: 0px !important;
margin-right: 0px !important;
padding: 0px !important;
-moz-border-radius: 0px !important;
}
.tab-icon-image {
-moz-margin-start: 1px !important;
-moz-margin-end: 1px !important;
width: 12px !important;
height: 12px !important;
}
.tab-text-container {
-moz-margin-end: 0px !important;
}
次にタブのカラーリングを設定。
Sleipnir 1.66でいう「アドレスバーでCtrl+Enter」相当の機能は、 スマートロケーションバーが代替。Enter Selectsを入れるとEnterを叩くだけで 最初の候補が開かれるので効率的。
検索エンジンやらスマートキーワードやら、Firefoxはどうも検索系が節操なく突っ込んである印象がある。 ページ内検索も、検索バーとは別だし。
ブラウザをFirefoxに移行したのにあわせ、RSSリーダーとしてNewsFoxを利用するようになった。 基本的にはキーボードのみで操作するようにしている。
後者3つは標準の機能では出来ないのでソース(newsfox.jar)の変更が必要になる。 クイックハックなのでソースを見て何をやっているか理解できないようであれば真似しない方が無難かと。
diff -ur orig/content/newsfox/newsfox.xul mod/content/newsfox/newsfox.xul
--- orig/content/newsfox/newsfox.xul 2008-09-22 13:04:38.000000000 +0900
+++ mod/content/newsfox/newsfox.xul 2009-01-09 23:41:54.000000000 +0900
@@ -99,10 +99,10 @@
label="&sCaddSearch;" command="cmd_addSearch"/>
<key id="add-group-key" modifiers="" key=""
mod0="" key0="" mod1="" key1="g" mod2="alt" key2="x"
- label="&sCaddGroup;" command="cmd_addGroup"/>
+ label="&sCaddGroup;" oncommand="scrollContent(500);"/>
<key id="delete-group-key" modifiers="" key=""
mod0="" key0="" mod1="shift" key1="G" mod2="alt shift" key2="X"
- label="&sCdeleteGroup;" command="cmd_deleteGroup"/>
+ label="&sCdeleteGroup;" oncommand="scrollContent(-500);"/>
<key id="add-feed-key" modifiers="" key=""
mod0="" key0="" mod1="" key1="f" mod2="alt" key2="c"
label="&sCadd;" command="cmd_addFeed"/>
@@ -114,7 +114,7 @@
label="&sCdeleteFeedFromGroup;" oncommand="deleteSingleFeedRow()"/>
<key id="refresh-key" modifiers="" key=""
mod0="" key0="" mod1="" key1="r" mod2="alt" key2="r"
- label="&sCrefresh;" command="cmd_checkFeedsslct"/>
+ label="&sCrefresh;" command="cmd_checkFeedsauto"/>
<key id="do-cancel-check-feeds-key" modifiers="" key=""
mod0="" key0="" mod1="" key1="q" mod2="alt" key2="q"
label="&sCcancel;" command="cmd_cancelCheckFeeds"/>
diff -ur orig/content/newsfox/ui.js mod/content/newsfox/ui.js
--- orig/content/newsfox/ui.js 2008-10-01 08:54:30.000000000 +0900
+++ mod/content/newsfox/ui.js 2009-01-09 23:36:30.000000000 +0900
@@ -754,6 +754,11 @@
feed.changed = false;
}
+function scrollContent(offset)
+{
+ document.getElementById("buildContent").contentWindow.scrollBy(0, offset);
+}
+
function openArticle()
{
var arttree = document.getElementById("newsfox.articleTree");
これとあわせ、オプションでShortcut Keys: standardとして生成した profiles/newsfox/accel.xmlを以下のように修正。
--- accel.xml.standard 2009-01-09 16:05:14.638875000 +0900 +++ accel.xml 2009-01-09 23:59:46.498250000 +0900 @@ -5,8 +5,8 @@ <feedtree disableKeyNavigation="true"/> <key id="add-search-group-key" modifiers="" key="s"/> - <key id="add-group-key" modifiers="" key="g"/> - <key id="delete-group-key" modifiers="shift" key="G"/> + <key id="add-group-key" modifiers="" key="v"/> + <key id="delete-group-key" modifiers="alt" key="v"/> <key id="add-feed-key" modifiers="" key="f"/> <key id="delete-feed-key" modifiers="shift" key="F"/> <key id="delete-feed-from-group-key" modifiers="shift" key="D"/> @@ -16,9 +16,8 @@ <key id="next-unread-key" modifiers="" key="n"/> <key id="prev-unread-key" modifiers="" key="p"/> <key id="open-unread-key" modifiers="" key="u"/> - <key id="options-key" modifiers="" key="o"/> <key id="home-key" modifiers="" key="h"/> - <key id="open-selected-key" modifiers="" key="v"/> + <key id="open-selected-key" modifiers="" key="o"/> <key id="delete-selected-key" modifiers="shift" key="V"/> <key id="manage-livemarks-key" modifiers="" key="l"/> <key id="toggle-filter-key" modifiers="" key="w"/>
Tipsとして、web filterとして「linkHTML;」だけを設定しておくとwキーでの 表示切り替え時にコンテンツがHTMLでそのまま表示されるようになる。
Git(ギット)勉強会メモに触発されてGitの利用を再検討。
あたりの機能が便利そうなので乗り換えることにした。 以前評価したときはCogitoでラップする必要があったりと使いづらいものだったけれど、 最近のバージョンはインターフェースもこなれていて悪くない。 ただ、Mercurialのリビジョンナンバー(ローカルでのみ有効な連番ID)相当の機能がないのが残念といえば残念か。
以下、試していて気になったポイントについて。
Gitのアーカイブに含まれるcontrib/hg-to-git/hg-to-git.pyで変換が可能。
「.gitconfigでaliasを設定すればよい」という解決策がすぐに見つかるものの、 それだとzsh上で補完が効かなくなるという問題がある。
これはグローバルエイリアスを設定して解決。
$ alias -g ann=annotate $ alias -g ci=commit $ alias -g co=checkout $ alias -g di=diff $ alias -g st=status
CVS HEADでは実装されているので、 そこから_gitを持ってきてfpathに配置すればOK。
revertの機能がMercurialのそれと異なるので注意。
$ hg revert --no-backup
に対応するコマンドは
$ git reset --hard (HEAD)
になる。
車輪の再発明のような気もするけれど。
次のようなWSFファイルを作成する。
<job id="sample">
<script language="JScript" src="pre_prototype.js"/>
<script language="JScript" src="prototype.js"/>
<script language="JScript" src="post_prototype.js"/>
<script language="JScript">
(3).times(function(i) {WScript.Echo(i)});
</script>
</job>
prototype.jsはブラウザで動かすことを前提とした作りになっているために、そのままではWSHでロードすることができない。 適当にダミーオブジェクトを定義する必要あり(pre_prototype.js)。
// For prototype.js version 1.6.0
var document = {
createElement: function() {return {appendChild: function(){}}},
createTextNode: function() {return {}},
createEvent: function() {return {__proto__: {}}},
write: function() {return {}},
getElementById: function() {return {}}
};
var window = {};
var navigator = {appVersion: ""};
var Element = {};
また、オートメーションオブジェクトにtoStringプロパティが定義されておらず Object.inspectの呼び出しに失敗するので、該当関数を再定義してみる(post_prototype.js)。
Object.extend(Object, {
inspect: function(object) {
try {
if (object === undefined) return 'undefined';
if (object === null) return 'null';
return object.inspect ? object.inspect() :
(object.toString ? object.toString() : "#<UnknownObject:" + object + ">");
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
}
}
});
ruby-breakpointはコード中に"breakpoint"と書いておくと実行時にその環境でreplに突入してくれるというもの。 オリジナルは複数行にわたる入力やdRubyを用いたリモートアクセスもサポートするが、そこまで考えなければ簡単に似たようなものが用意できる。
(function(i) {eval(breakpoint)})(0);
実行結果。
> i + 1 1
var breakpoint =
'for(;;) {' +
' WScript.StdOut.Write("> ");' +
' if (WScript.StdIn.AtEndOfStream) break;' +
' var expr = WScript.StdIn.ReadLine();' +
' try {' +
' WScript.StdOut.WriteLine(Object.inspect(eval(expr)));' +
' }' +
' catch (e) {' +
' WScript.StdOut.WriteLine("Error Code: " + e.number);' +
' WScript.StdOut.WriteLine(e.description);' +
' }' +
'}';
例外をキャッチしてスタックトレースを付与する関数でラップする。
function func1(i, j) {
(function () { func2("foo") })([]);
}
function func2(i, j) {
throw new Error("ERROR");
}
func2 = throwsInformationalError(func2);
var Test = Class.create({
func3: throwsInformationalError(function(i, j) {
this.func4("bar");
}, "func3"),
func4: throwsInformationalError(function(i, j) {
throw new Error("ERROR");
}, "func4")
});
それぞれ呼び出したときの実行結果。
func1();
test.wsf(149, 10) Microsoft JScript 実行時エラー: ERROR
--- Stack Trace ---
func2('foo')
[anonymous]([])
func1()
(new Test()).func3()
test.wsf(149, 10) Microsoft JScript 実行時エラー: ERROR
--- Stack Trace ---
func4('bar')
func3()
Object.extend(Function.prototype, {
getName: function () {
if (this.name) return this.name;
var regmatch = this.toString().match(/^function\s*(\w+)/);
return regmatch ? regmatch[1] : '[anonymous]';
}
});
function throwsInformationalError(fn, fn_name) {
function _throwsInformationalError() {
try {
return fn.apply(this, arguments);
}
catch (e) {
if (! e.informational) {
var stack = [];
for (var i = arguments.callee; i; i = i.caller) {
var args = [];
for (var j = 0; j < i.arguments.length; j++) {
args.push(Object.inspect(i.arguments[j]));
}
var _fn = (i == arguments.callee) ? fn : i;
if (_fn.getName() != "_throwsInformationalError") {
stack.push(_fn.getName() +"(" + args.join(", ") + ")");
}
}
e.description += "\n--- Stack Trace ---\n" + stack.join("\n");
e.informational = true;
}
throw e;
}
};
if (fn_name) {
fn.name = fn_name;
}
return _throwsInformationalError;
}
JScriptのfor...in文とVBScriptのFor Each...Next文は言語仕様的には似たようなものだけど、 その実装には決定的な違いがある。その差を埋めるためにEnumeratorが用意されてはいるけれど、 それでもなおカバーしきれないオブジェクトが存在する。ADSIによる属性アクセスなどがそれ。
> var o = GetObject("LDAP://DC=example,DC=com")
#<UnknownObject:>
> o.objectClass
[object Object]
> for (var i in o.objectClass) {WScript.Echo(i)}
undefined
> new Enumerator(o.objectClass)
オブジェクトがコレクションではありません。
ここで各要素にアクセスする方法を調べてみるも、MicrosoftのサンプルはVBScriptでFor Each...Nextしているものばかり。 しょうがないのでVBScript側でFor Each...Next文相当のプロシージャを用意しておいてWSFファイルで読み込んで利用する。 *1
Sub foreach(obj, fn)
Dim i
For Each i In obj
Call fn(i)
Next
End Sub
JScript側からはこのように。
> foreach(o.objectClass, function(i) {WScript.Echo(i)})
top
domain
domainDNS
undefined
*1 ただ、この例ならばo.objectClass.toArray()とやればアクセス可能。
Mercurial 0.9.4 released!より。 あまり取り上げられていないようだけど、地味に使いづらいところだったのでありがたい。
クイックハックだけどなかなか便利に。
--- mobileimap.in.orig 2005-05-22 16:04:40.000000000 +0900
+++ mobileimap.in 2007-05-19 21:55:10.000000000 +0900
@@ -862,7 +862,7 @@
:seq => seq)
begin
generate_from_line(g, seq, nmessages) +
- g.a(:href => url) {
+ g.a(:href => url, :accesskey => nmessages - nth(seq) + 1) {
emphasize(g, compact_subject(get_subject(seq)).escapeHTML)
} + g.hr
rescue Exception => e
@@ -1436,6 +1436,7 @@
" " + g.a(:href => "#bottom") { "↓" } +
g.br + generate_seen_mode(g) + g.hr +
generate_search_form(g) +
+ g.a(:name => 0, :href => "#0", :accesskey => 0) { "→" } + g.br +
generate_list(g, nmessages, messages) +
navi + " " +
g.a(:href => generate_url(:command => "folders",
@@ -1725,6 +1726,7 @@
g.a(:href => "#bottom") { "↓" } + " " +
generate_seen_mode(g) + " " + g.hr +
generate_fsearch_form(g) +
+ g.a(:name => 0, :href => "#0", :accesskey => 0) { "→" } + g.br +
folders.map {|folder|
line = g.font(:color => @seq_color) { sprintf("%03d", i) } +
if get_unseen_nmessages(folder) > 0 then
@@ -1733,7 +1735,8 @@
" "
end +
g.a(:href => generate_url(:folder => folder,
- :unseen_only => @query.unseen_only)){
+ :unseen_only => @query.unseen_only),
+ :accesskey => i){
simplify_folder_name(folder)
} + g.br
i += 1
メモ。
[HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice] "Progid"="Sleipnir.API"
ちょくちょく利用されているようなので、Gauche 0.8.9リリースにあわせて更新。
ついでに任意のバージョンのドキュメントを用意したい人向けにこの生成方法について簡単に書いておきます。 キモはisearch.html中の<table id="all">の中身なんですが、この部分はHTML化されたGaucheユーザリファレンスを適当なディレクトリに展開したあとその中で
grep 'class="summary-letter"' gauche-refj*|\ grep --only-matching 'gauche-refj_[0-9]*\.html'|sort|uniq|\ xargs grep --no-filename '^<tr><td></td><td valign="top"><a href="gauche-refj'|\ sed 's/a href/a target="ref" href/g'
としたものを埋め込んでいます。あとは必要なファイルを落として同じディレクトリに置けば完成。
CVSによるオープンソース開発にも話題が出てるけど、 実際にやってるって話をとんと聞かない/etcのバージョン管理。これをMercurialを使って試してみた。
この用途にCVSやSubversionではなくMercurialを使うメリットは少なくとも2つ考えられる。
試してないけど、サーバ間で変更の同期を取るのにも使えるかも。
で、初めて本格的にMercurialを使い始めたのだけど、いろいろカスタマイズしたいところが出てきた。
標準のhg logの出力は次のような形で、コミットメッセージはそのsummary(=1行目)が出力される。 つまり、コミットメッセージが複数行に渡る時はそのすべてを確認することが出来ない。
changeset: 3:b9d48fc01c60 user: User <user@example.com> date: Thu Jan 05 21:00:27 2007 +0900 summary: commit message line1.
-vオプションを使うというのも一つの手だけど、もう少し柔軟に何とかならないものかと調べてみると--styleオプションが使えそうだということが分かった。
hg log [OPTION]... [FILE]
options:
--style display using template map file
オリジナルのテンプレートはDebianの場合/usr/share/mercurial/templates/map-cmdline.defaultにあるのでそいつをカスタマイズして使えばいいみたい。
# mkdir ~/.hg
# cp /usr/share/mercurial/templates/map-cmdline.default ~/.hg/map-cmdline.custom
# vi ~/.hg/map-cmdline.custom
-changeset = 'changeset: {rev}:{node|short}\n ... summary: {desc|firstline}\n\n'
+changeset = 'changeset: {rev}:{node|short}\n ... description:\n{desc|strip}\n\n'
# cd /etc
# hg log --style ~/.hg/map-cmdline.custom
changeset: 3:b9d48fc01c60
user: User <user@example.com>
date: Thu Jan 05 21:00:27 2007 +0900
description:
commit message line1.
commit message line2.
うん、出来た。標準でこのフォーマットを使いたいなら次のようにする。
# vi ~/.hgrc [defaults] log = --style /root/.hg/map-cmdline.custom