ocamljsが生成したソースをclosure compilerで圧縮する

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 が期待通り動作するように修正する方法を書く。

修正方法

  1. Javaコンパイラとantを準備する.
  2. closure compilerのソースをダウンロードする.
  3. 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 と等価ではない。

ブラウザで試す(ideone)

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拡張について

ツールバーの1クリックでオフラインキャッシュを消去できるので便利だと思われる。そのうちHTML5とオフラインキャッシュが流行りだすと使いたい場面が増えるかもしれない。ただ私は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の策定がどのような状況なのかは知らないが).SafariHTML5を軽く試した限りでは,まともに動作しているように見えた.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で,少しのミスでページのページのリロードがされなくなってしまう.曰く,

  1. manifest ファイルにエラーがある場合にはリロードしない.ここでエラーとは
    • 構文エラー
    • manifest ファイルに記述されているファイルが存在しない
    • manifestファイルに記述されているJavaScriptファイルに構文エラーがある???(そう見えるときがある)
  2. 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をポチポチ書いた。