ocamljsが生成したソースをclosure compilerで圧縮する
- ocamljsは OCamlからJavaScriptへのトランスレータ。
- closure compilerは JavaScriptのコードを圧縮/最適化するツール。
ocamljsが生成するJavaScriptのソースは大きくなりがちなので、minifier等でできるかぎり小さくできることが望ましい。その点で closure compiler はかなり良い。 スペースを削除するだけだと 56k が 36k にしかならないものが、 最適化すると 17k 。 まずまずだ。
以下の不具合は head版 ocamljsでは発生しなくなっています。Jakeに感謝!
ocamljsからコードを生成してclosure compilerを通すと動作しなくなる
しかし ocamljsから生成してclosure compilerを通したソースをロードすると
TypeError: Result of expression 'd.apply' [undefined] is not a function.
というエラーが出る(SIMPLE_OPTIMIZATION時。 --compilation_level WHITESPACE_ONLY でスペースを削除するだけならばうまくいく)。
これは、ocamljsが生成したコードに 一部 closure compiler が対応していないのが原因だった。
以下に closure compiler が期待通り動作するように修正する方法を書く。
修正方法
- Javaのコンパイラとantを準備する.
- closure compilerのソースをダウンロードする.
- src/com/google/javascript/jscomp/RemoveUnusedVars.java から
for (Scope fnScope : allFunctionScopes) {
removeUnreferencedFunctionArgs(fnScope);
}
の三行をコメントアウトする。
→ ant でビルドする
原因
ocamljsがカリー化された関数を呼び出すとき、 関数の引数の数を表す length プロパティを用いるが、 closure compiler は関数の未使用の引数を削除してしまうので、lengthプロパティの値が変わってしまうのが原因。
closure compiler は 関数の引数の数を表す length プロパティの使用に対応していない(変換が意味を保存しない)。
参考: http://code.google.com/p/closure-compiler/issues/detail?id=253
その他
ocamljsは互換性のためブラウザではまず使わない標準ライブラリ関数も含んでいる。さすがに closure compilerではこれらを削除できないようだ。 コンパイル前の段階でこれを削ってしまえばもっと小さくなるだろう。
ビットとview pattern
小ネタ。 ビットパターンをHaskellでパターンマッチできたらいいよね、ということでview patternを使ってみた。
{-# LANGUAGE ViewPatterns #-} import Data.Bits -- | ビット。 I は 1, O は 0 data B = I | O deriving Show -- | 8ビット。 左がMSB, 右がLSB data Byte = B B B B B B B B B deriving Show bool2b :: Bool -> B bool2b True = I; bool2b False = O bit' :: Bits a => Int -> a -> B bit' i b = bool2b $ testBit b i -- | view patternで使う関数 bit8 :: Bits a => a -> Byte bit8 b = B (bit' 7 b) (bit' 6 b) (bit' 5 b) (bit' 4 b) (bit' 3 b) (bit' 2 b) (bit' 1 b) (bit' 0 b) -- 使い方 isMsbOn :: Bits a => a -> Bool -- 名前は適当 isMsbOn (bit8 -> B I _ _ _ _ _ _ _) = True -- view pattern isMsbOn _ = False isLsbOn :: Bits a => a -> Bool isLsbOn (bit8 -> B _ _ _ _ _ _ _ I) = True -- view pattern isLsbOn _ = False
Main> isMsbOn (128::Int) True Main> isMsbOn (100::Int) False Main> isLsbOn (100::Int) False Main> isLsbOn (101::Int) True Main>
Tickモナドとモナド則
まだHaskellをはじめて間もないころ、「bindの度に何かするモナド」を考えた。例えば、
action = do putStrLn "one." putStrLn "two." putStrLn "three."
というコードを実行すると
one Hello! two Hello! three
と出力するような。応用すれば、「定期的に yield するスレッド」とかを作れそうだ。 というかOCamlのLWT(lightweight thread)とかは多分そういうことをやっているはず。
まず思いつくのは、bindの定義を素朴に書き換えることだ。試してないけどこんな感じに:
newtype MyIO a = MyIO {ext::IO a} instance MyIO where return = MyIO . return MyIO m >>= f = MyIO (m >>= \a -> putStrLn "Hello!" >> ext (f a))
しかしこれはモナド則を満たさない。具体的には、returnが >>= の単位元にならない。 m >>= return は "Hello!" を余分に出力するので m と等価ではない。 同様に return a >>= f が f a と等価にならない。
TickT モナド変換子
id:yts さんは(ずいぶん昔の話だが)私に こちらで丁寧にも TickTモナド変換子というものを ReaderTの変種として提示してくださった。tickという関数でIOアクションをliftしたときのみ、環境に入れておいた仕事をする。
一方、当初試していたように、
- bindの定義で何か他の仕事をさせる
- モナド則を満たす
ようなモナドもあっていいはずだ(← やってる事はytsさんのTickTと変わらない)。
というわけで下のコードを書いた。多分モナド則は満たしているはず。
ここで定義した Tick はwrapMがliftになるモナド変換子(MonadTransのインスタンス)のように見えるが、多分違う。 例えば lift (return a) は return a と等価であってほしいはずだけど(多分)、wrapM (return a) は余分に仕事をさせるので return a と等価ではない。
module Tick(Tick(), runTick, wrapM, w, wrap, p) where import Control.Monad -- 例で使う import Control.Monad.State -- 例で使う -- | `Tick' monad. Tick is not a monad transformer!! newtype Tick m a = Tick {untick :: m () -> TickCase m a} data TickCase m a = Wrap (m a) | Return a -- | Runs a tick monad. runTick :: Monad m => Tick m a -> m () -> m a runTick (Tick f) t = extTickCase (f t) where extTickCase (Wrap m) = m extTickCase (Return a) = return a tryAddTick :: Monad m => TickCase m a -> m () -> m a tryAddTick (Return a) _ = return a tryAddTick (Wrap m) t = t >> m instance Monad m => Monad (Tick m) where return a = Tick (\_ -> Return a) Tick f >>= g = Tick (\t -> case f t of Return a -> untick (g a) t Wrap m -> Wrap (m >>= \a -> tryAddTick (untick (g a) t) t)) wrapM, w :: Monad m => m a -> Tick m a wrapM m = Tick (\_ -> Wrap m) w = wrapM wrap, p :: Monad m => a -> Tick m a wrap = wrapM . return p = wrap changeTick :: Monad m => (m () -> m ()) -> Tick m a -> Tick m a changeTick f m = Tick (\t -> untick m (f t)) ---------- -- Example ---------- tick1 = putStr "tick! " tak1 = putStr "tak! " ex1 = do wrapM (putStr "Hello, "); wrapM (putStrLn "World!") -- *Tick> runTick ex1 tick1 -- Hello, tick! World! -- -- *Tick> runTick ex1 tak1 -- Hello, tak! World! ex2 = mapM (\x -> do w$ print x; return x) [1..10] -- *Tick> runTick ex2 tick1 -- 1 -- tick! 2 -- tick! 3 -- tick! 4 -- tick! 5 -- tick! 6 -- tick! 7 -- tick! 8 -- tick! 9 -- tick! 10 -- [1,2,3,4,5,6,7,8,9,10] -- *Tick> runTick ex2 tak1 -- 1 -- tak! 2 -- tak! 3 -- tak! 4 -- tak! 5 -- tak! 6 -- tak! 7 -- tak! 8 -- tak! 9 -- tak! 10 -- [1,2,3,4,5,6,7,8,9,10] inc :: State Int () inc = State (\s -> ((), s+1)) ex3 = runTick (foldM (\x y -> p$ x+y) 0 [1..10]) inc -- *Tick> runState ex3 0 -- (55,9)
Firefoxで特定のページのオフラインキャッシュとlocalstorageをクリアするアドオン
Firefoxにおいて、HTML5のアプリケーションキャッシュ(オフラインキャッシュ)をクリアするには、メニューの環境設定→詳細→ネットワークを開き、「今すぐ消去」をクリックする。しかしHTML5アプリの開発で毎回この画面を開くのは面倒である。
この操作をショートカットするために、HTML5のオフラインキャッシュを強制的にクリアするFirefoxのアドオン(拡張機能)を適当に作って置いた(動作の詳細は昨日の記事に書いた)。ツールバーのボタンをクリックするとapplication cacheを消去した後にキャッシュマニフェスト(cache manifest)のURLを表示する。 ついでにlocal storage (DOM storage)もクリアする。
以下からダウンロード:
Firefox HTML5 Offline-Cache and Local Storage Clearing Add-on
zip形式で置いてある(Google Sitesの制限を回避するため)。 ダウンロード後に拡張子を .xpi に変更して Firefox にドラッグ&ドロップすればインストールできる。
Firefox 3.6で HTML5 の オフラインキャッシュをクリアする
(追記:アドオンを作った。)
HTML5 には オフラインでwebアプリケーションを動作させるためにファイルをキャッシュする手段が指定されている. cache manifest というファイル に,「どのファイルをキャッシュするか」を記述しておき、htmlタグの manifest 属性で指定する.
例:
test.manifest
CACHE MANIFEST test.html a.jpeg b.jpeg ...
test.html
<html manifest='test.manifest'> ...
このように書いておけば インターネット接続が無い環境でも test.html が閲覧できるようになり,たとえば接続が途切れやすいモバイル環境でもハッピーになれるというわけだ.
オフラインキャッシュを使ったwebアプリケーションのサンプルが hacks.mozilla.orgで公開されている. タスクリストのwebアプリで,LANケーブルを引っこ抜いてもタスクリストが管理できる.
しかしながら,各ブラウザでのHTML5の実装状況はまちまちであるような感じらしい(HTML5の策定がどのような状況なのかは知らないが).SafariのHTML5を軽く試した限りでは,まともに動作しているように見えた.Appleの本気度の現れだろうか.一方でFirefoxのcache manifestは今のところ少し扱いづらい.HTML5を詳しく調べたわけではないが使っていて非常に混乱する部分がある.
そこで,Firefoxのオフラインキャッシュの機能について,さしあたり現在の挙動振る舞いについてまとめた.未だexperimentalな機能なので将来的には改善されると思うけれど,それまでのつなぎにはなるかもしれない.
キャッシュの更新
Firefoxの cache manifest について
Stackoverflow - How to properly invalidate an HTML5 Cache Manifest for online/offline web apps? で議論されている(ここにポストされている「キャッシュ更新検出スクリプト」は結構便利).いわく,
cache manifest に列挙したファイルはリロード時にもフェッチされない
ということらしい.
どうやってキャッシュを更新するか
MDCで解説されているとおり,一応,キャッシュを更新する方法はある. cache manifestを更新すれば,ページを構成する他のファイルの更新がチェックされ必要なファイルがダウンロードされるようだ.しかし 何をもって cache manifest の「更新」とするのかがかなりsensitiveで,少しのミスでページのページのリロードがされなくなってしまう.曰く,
- manifest ファイルにエラーがある場合にはリロードしない.ここでエラーとは
- 構文エラー
- manifest ファイルに記述されているファイルが存在しない
- manifestファイルに記述されているJavaScriptファイルに構文エラーがある???(そう見えるときがある)
- manifest ファイルのコメントや空行は無視される.コメントが修正されてもリロードされない(MDCの説明と違う!).
…つまり,cache manifestに書いたファイルは全てサーバー上に準備しておかなければならず,さらにcache manifestに記述したエントリを増やすか削るかしないと,ページのリロードを促すことはできない.面倒である.
またFirefoxのリロードボタン(F5やCommand-R)は少なくとも2回は試したほうが良いようだ.
オフラインキャッシュをクリアする方法
いちいちcache manifestを更新するよりは,いっそのこと オフラインキャッシュをクリアしてしまうのも一手だ.普通のやり方と,私が少しググって考えたもう一つのやり方を紹介する.
方法1
メニュー Tools -> Clear Recent History から Site Preferences (Cache ではなく) にチェックを入れて Clear Now をクリックする.
…しかしこの方法では全てのサイトのオフラインキャッシュがクリアされてしまう.
方法2
次のJavaScriptをエラーコンソールで実行する.
Components.classes["@mozilla.org/network/application-cache-service;1"].getService(Components.interfaces.nsIApplicationCacheService).getActiveCache('マニフェストファイルのURL').discard();
この方法であれば目的のページのキャッシュのみをクリアできる.
適当なAdd-onの開発が待たれる.
方法3
about:cache?device=offline で表示されるディレクトリにあるindex.sqlite の中身をうまく消す。 方法はわからないがこのファイルを消してしまっても大丈夫かもしれない(試していない)
別の話
cache manifestでキャッシュされたページからXMLHttpRequestでcache manifestにないファイルを取りにいくとエラー (Error: uncaught exception: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIXMLHttpRequest.send]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" )
なぜ?
CPSチェイン
追記:「東京Node学園#1「非同期プログラミングの改善」のエッセンス」というスライドで、似たようなテクニックが使われているのを見た(p.32-)。 そんなにスジは悪くないのかもしれない。
JavaScriptでは非同期的に関数を呼ぶことが多い。例えばimgタグのロードが終わったら次にAを処理して、それが終わったらBをして…など。
こういうときCPS形式でプログラムを書くはめになる。
func1(arg1, function(res1){ func2(arg2, function(arg3, res2){..})})
かようにJavaScriptでは関数リテラルがすこぶる扱いづらい -- 'function' とか 'return' とか {} のネストとか…あとカリー化するにも一苦労 -- JavaScriptプログラマはどう書くんだろう? こういう非同期呼び出しをうまく同期的に記述するためのsyntax sugarがほしい。 第一級継続があればうまいこと書けるのだろうけど。
さしあたり function(引数,継続) {} という形の関数を逐次的に呼び出す関数を書いてみた。
function chain() { function cpsbody(a) { if(a.length<2) { return a[0]; } return a[1](a[0],function(r){return cpsbody([r].concat(a.slice(2)));}) } var args = Array.prototype.slice.call(arguments); // making array from arguments return cpsbody(args); }
こんな風に使う。
function f1(arg,ifdone) { return ifdone(arg+" f1 "); } function f2(arg,ifdone) { return ifdone(arg+" f2 "); } alert(chain("start",f1,f2,f1,f2)); // "start f1 f2 f1 f2 " がダイアログボックスに出る
OM2010 in Nagoya近し
名古屋でのOCaml meeting 2010が近いけれど、何かと忙しくて内容にcontributeできそうにない…。OCamlJSを使って何かやるつもりだったけど間に合わない…。
などといいつつ、気分転換にJavaScriptをポチポチ書いた。