2012年2月21日火曜日

Playing with Dartium part 2

ひき続きDartiumでお遊び。今回はIsolateを試してみた。が、結論から言うと目論みははずれてうまくいなかったです。

Isolate

Dartはつねにシングルスレッドで実行される(仕様書にも"Dart code is always single threaded."て書いてある)んだけど、何らかの処理を並行して実行したい場合というのが出てくる。他の言語だったらスレッドなりの出番なんだけど、dartではIsolateという仕組みを使うみたい。

Isolate自体はスレッドのようなものだと考えれば良くて(実際にスレッドをつくるかどうかはIsolateの設定次第)、使いかたはIsolateクラスをextendしてmain関数を定義するだけ。まあその辺の作法の詳細はドキュメントサンプルを参照。

RemarkDisplayerIsolate

ということで前回つくったRemarkDisplayerをIsolate化して使おうと思ったんだけど、その過程でちょっと予想外なエラーにぶちあったった。Dartiumごとクラッシュしたのでログだけ載せる。

Unhandled exception:
DOM access is not enabled in this isolate
 0. Function: 'Utils.window' url: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/dart/client/dom/src/native_DOMImplementation.dart' line:20 col:3
 1. Function: '::get:window' url: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/dart/client/dom/src/native_GlobalProperties.dart' line:6 col:36
 ...

IsolateからはDOMに触れないって話。エラーメッセージ読むかぎりだと設定で有効化できそうな雰囲気もあるけど、予想ではたぶんできない。C#でGUIを作るときもそうだけど、バックグラウンドのスレッドからは直接UI要素に触らせてくれない場合が多い。

感想

まあとりあえずIsolateの使いかたそのものは何となくわかったのでよしとする。でもIsolate自体はあんまり使いやすくないな…わかりづらい。GoのChannelみたいにPortが双方向だともっとスッキリしてわかりやすいと思うんだけども。

コード

途中で例外飛ぶけど、失敗例としてコードを掲載しておく。

  • RemarkDisplayer.dart
    #library("RemarkDisplayer");
    
    #import("dart:html");
    
    class MessageTypes {
        static final int ERROR = -1;
        static final int OK = 0;
        static final int SETUP = 1;
        static final int INIT = 2;
        static final int DISPLAY = 3;
    }
    
    class DisplayerState {
        static final int SETUP = 1;
        static final int INITIALIZED = 2;
        static final int DISPLAYING = 3;
    }
    
    /**
     * Class that attempted to manipulate DOM in an Isolate.
     */
    class RemarkDisplayerIsolate extends Isolate {
        RemarkDisplayerIsolate() {}
    
        void main() {
            port.receive((message, SendPort replyTo) {
                    dispatch(message, replyTo, port.toSendPort());
            });
        }
    
        void dispatch(var message, SendPort replyTo, SendPort myPort) {
            int action = message['action'];
            var arg = message['arg'];
            switch(action) {
            case MessageTypes.SETUP:
                _displayer = new RemarkDisplayer(arg);
                replyTo.send(DisplayerState.SETUP, myPort);
                break;
            case MessageTypes.INIT:
                _displayer.initialize(arg); // EXCEPTION FIRED HERE
                replyTo.send(DisplayerState.INITIALIZED, myPort);
                break;
            case MessageTypes.DISPLAY:
                _displayer.display(arg);
                replyTo.send(DisplayerState.DISPLAYING, myPort);
                break;
            }
        }
    
        RemarkDisplayer _displayer;
    }
    
    /**
     * Class that manages remark display
     */
    class RemarkDisplayer {
        RemarkDisplayer(int numberOfRemarks) {
            _numberOfRemarks = numberOfRemarks;
            _remarkList = new List<Element>();
            _currentRemark = 0;
        }
        
        /**
         * Create remark nodes under the tag with the given ID
         */
        void initialize(String stageID) {
            var stage = document.query(stageID);
            for (int i=0; i<_numberOfRemarks; i++) {
                var tag = '<pre class="remark" id="remark${i}" draggable="true"/>';
                var elem = new Element.html(tag);
                stage.nodes.add(elem);
                _remarkList.add(elem);
            }
        }
        
        /**
         * Display given remark at the current node
         */
        void display(String remark) {
            var node = _remarkList[_currentRemark];
            var durationMS= 5000;
            node.innerHTML = remark;
            node.style.visibility = "visible";
            node.style.animationName = "fade, hslide";
            node.style.animationDuration = "${durationMS}ms";
            node.style.animationTimingFunction = "linear";
            node.style.animationFillMode = "forwards";
            
            // Replace node with a clone to restart animation
            var newNode = node.clone(true);
            node.replaceWith(newNode);
            _remarkList[_currentRemark] = newNode;
            
            // proceed to next remark
            _currentRemark++;
            if (_currentRemark >= _numberOfRemarks) {
                _currentRemark = 0;
            }
        }
        
        int _numberOfRemarks;
        get numberOfRemarks() => _numberOfRemarks;
        
        List<Element> _remarkList;
        int _currentRemark;
    }
    
  • main.dart
    #library('displayer');
    
    #import("dart:html");
    #import("RemarkDisplayer.dart");
    
    void dispatch(var message, SendPort replyTo, SendPort myPort) {
        switch(message) {
        case DisplayerState.SETUP:
            var msg = { 
                "action" : MessageTypes.INIT,
                "arg" : "#stage"
            };
            replyTo.send(msg, myPort);
            break;
        case DisplayerState.INITIALIZED:
            var msg = { 
                "action" : MessageTypes.DISPLAY,
                "arg" : "adsfasfas"
            };
            replyTo.send(msg, myPort);
            break;
        case DisplayerState.DISPLAYING:
            replyTo.close();
            break;
        }
    }
    
    void main() {
        final int MAX_NUMBER_OF_REMARKS = 10;
        final receivePort = new ReceivePort();
        receivePort.receive((var message, SendPort sendPort) {
                print("received ${message}");
                dispatch(message, sendPort, receivePort.toSendPort());
        });
    
        new RemarkDisplayerIsolate().spawn().then((SendPort sendPort) {
                var msg = { 
                    "action" : MessageTypes.SETUP,
                    "arg" : MAX_NUMBER_OF_REMARKS
                };
                sendPort.send(msg, receivePort.toSendPort());
         });
    }
    

2012年2月19日日曜日

Playing with Dartium

前にjavascriptで書いてた処理をdartに移植してDartiumで動かしてみた。やらんとしていたのはニコニコもどきだけど、書いてる途中にjavascriptに嫌気がさして投げたので色々と中途半端。とりあえず処理の内容は以下。

  1. 特定のid(今回だと"stage")を持つノードにいくつかノードを追加する。
  2. ボタンがクリックされるたびにTextArea内の内容を(最初に追加された)ノードにわりあてて、CSSアニメーションをくっつけて表示する。

完成図

Postをクリックするたびに麻呂がフェードアウトしながら右にスライドしていきます。スバラシイ。

html + dart

dartのコードはclassの使い方を試す意図もあったので、あえてclassにして書いてる。その過程で学んだことを数点書くと以下の通り。

  • Dart Style Guideには従っとけ。
  • 変数名の頭に"_"をつけるとprivateな変数になる。つけないと自動的にpublicになる。
  • intの変数を文字列表現に変換したいような場合は"$value"や"${value}"という書きかた(string interpolation syntax)をすると自動的に文字列変換してくれる。こういう仕様になった事情はDartの言語仕様を見ると書いてあって、抜粋すると以下の通り。

    The string interpolation syntax is designed to be familiar and easy to use, if somewhat awkward to parse. The intent is to encourage its use over alternatives such as s1 + s2. In a dynamically typed language, the use of the + operator requires dynamic dispatch. In contrast, in the case of string interpolation we can statically determine that the string concatenation operation is required, making the operation more efficient. Even more importantly, it helps the system to determine if other uses of + are numeric, helping the implementation speed up those operations. This is especially crucial for a language that must be efficiently compiled into Javascript.

    要するに対象の変数が文字列なのか数字なのかがより明確にできるから、周辺の処理を高速化しやすくなる、という話みたい。

<!doctype html>
<head>
    <meta charset="utf-8">
    <title>dartlang test</title>
    <link rel="stylesheet" type="text/css" href="./css/bootstrap.min.css" />
    <link rel="stylesheet" type="text/css" href="./css/client.css" />
</head>
<body>
    <script type="application/dart" >
        #import("dart:html");
        /**
          * Class that manages remark display
          */
        class RemarkDisplayer {
            RemarkDisplayer(int numberOfRemarks) {
                _numberOfRemarks = numberOfRemarks;
                _remarkList = new List<Element>();
                _currentRemark = 0;
            }
            
            /**
             * Create remark nodes under the tag with the given ID
             */
            void initialize(String stageID) {
                var stage = document.query(stageID);
                for (int i=0; i<_numberOfRemarks; i++) {
                    var tag = '<pre class="remark" id="remark${i}" draggable="true"/>';
                    var elem = new Element.html(tag);
                    stage.nodes.add(elem);
                    _remarkList.add(elem);
                }
            }

            /**
              * Display given remark at the current node
              */
            void display(String remark) {
                var node = _remarkList[_currentRemark];
                var durationMS= 5000;
                node.innerHTML = remark;
                node.style.visibility = "visible";
                node.style.animationName = "fade, hslide";
                node.style.animationDuration = "${durationMS}ms";
                node.style.animationTimingFunction = "linear";
                node.style.animationFillMode = "forwards";

                // Replace node with a clone to restart animation
                var newNode = node.clone(true);
                node.replaceWith(newNode);
                _remarkList[_currentRemark] = newNode;

                // proceed to next remark
                _currentRemark++;
                if (_currentRemark >= _numberOfRemarks) {
                    _currentRemark = 0;
                }
            }
            
            int _numberOfRemarks;
            get numberOfRemarks() => _numberOfRemarks;

            List<Element> _remarkList;
            int _currentRemark;
        }

        void main() {
            final int MAX_NUMBER_OF_REMARKS = 10;
            var displayer = new RemarkDisplayer(MAX_NUMBER_OF_REMARKS);
            displayer.initialize("#stage");
            var button = document.query("#postRemark");
            TextAreaElement textNode = document.query("#remarkText");
            button.on.click.add((Event e) {
                    displayer.display(textNode.value);
            });
         }
    </script>
    <div class="topbar">
        <div class="fill">
            <div class="container">
                <a class="brand" href="#">dartlang test</a>
            </div>
        </div>
    </div>
    
    <div class="container">
        <div class="content" >
            <form action="">
                <textarea class="xxlarge" rows="23" id="remarkText">
        ___|二ニー-、、;:;:;:;:;:;:;:;:;:;:;:;:;:;:;:;:;:;:|;::;:;:;:;:;:;:;:;:;:;:;:;:l
        /rヽ三三三三三─‐-- 、;:;:;:;:;:;:;:|;:;:;:;:;:;:;:;:;:;:;:;:;:;l
        ',i ,-三三三三三、   _,.ニ、ー-、!;: -‐二 ̄彡′
        ',、、ヾ三三'" ̄ ̄   `ー‐"    ヾ-'"  .〉′
        ヽ ヽヾ三,'    :::..,. -‐- 、     _,,..-‐、、,'
         `ー',ミミ     ::.弋ラ''ー、   i'"ィ'之フ l
         /:l lミミ     ::::.. 二フ´   l ヽ、.ノ ,'     
      ,.-‐フ:::::| |,ミ             l      /       
     /r‐'":::::::::| |ヾ        /__.   l    /      
 _,. -‐"i .|::::::::::::::::::',.',. \        ⌒ヽ、,ノ   /ヽ,_             
"    l ヽ:::::::::::::::::ヽヽ. \   _,_,.、〃  /l |    ___,. -、
     ',\\:::::::::::::::ヽ\  \  、. ̄⌒" ̄/:::::| |    ( ヽ-ゝ _i,.>-t--、
     \\\;::::::::::::\\  `、.__  ̄´ ̄/::::::::::l |    `''''フく _,. -ゝ┴-r-、
       ヽ \`ー-、::::::ヽ ヽ    ̄フフ::::::::::::::ノ ./   ,.-''"´ / ̄,./´ ゝ_'ヲ
          `ー-二'‐┴┴、__/‐'‐´二ー'".ノ   / _,. く  / ゝ_/ ̄|
               ̄`ー─--─‐''" ̄      / にニ'/,.、-t‐┴―'''''ヽ
                              /  /  .(_ヽ-'__,.⊥--t-⊥,,_
                              /  /  /   ̄   )  ノ__'-ノ
                             /      /    ゝニ--‐、‐   |
                            /           /‐<_   ヽ  |ヽ </textarea>
                <button class="btn primary" id="postRemark" type="button">Post</button>
            </form>
        </div>

        <div id="stage"></div>
    </div>
</body>
</html>

css

bootstrapなcssを除き、自分で新たに定義したcssも掲載する。

html, body {
    background-color: #eee;
}
body {
    padding-top: 40px; /* 40px to make the container go all the way to the bottom of the topbar */
}
.container > footer p {
    text-align: center; /* center align it with the container */
}

.remark {
    background : rgba(0,0,0,0); /* transparent background*/
    position: absolute;
    width: auto;
    height: auto;
    font-family: "IPA モナーPゴシック", monospace;
}

/* Fading animation */
@-webkit-keyframes fade {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

/* vertical scroll animation */
@-webkit-keyframes vslide {
  0%   { -webkit-transform: translateY(0px); }
  100% { -webkit-transform: translateY(100px); }
}

/* horizontal scroll animation */
@-webkit-keyframes hslide {
  0%   { -webkit-transform: translateX(0px); }
  100% { -webkit-transform: translateX(1000px); }
}

感想

classに変数やら処理をまとめて隠蔽できるのは気持ちいいっすね。

2012年2月18日土曜日

Trying Dartium on Ubuntu Linux 11.10

Dartium(DartのVMが搭載されたChromium)のバイナリが公開されたので、早速試してみた。環境は32bitのUbuntu Linux 11.10で、unameは↓ね。

検証環境

$ uname -a
Linux masato-xubuntu 3.0.0-16-generic-pae #28-Ubuntu SMP Fri Jan 27 19:24:01 UTC 2012 i686 i686 i386 GNU/Linux

環境構築

バイナリを用意してくれたおかげで非常に簡単。

  1. 適切なバイナリを落としてくる。今回はこれを使用。dartium-lucid64.zipとかいう名前だから一瞬64bitじゃないとダメかと思ったけど、32bit OSでも普通に動いてる
  2. zip解凍してDartium起動する。zip解凍すると色々でてくるけど、重要なのはchromeだけ。ファイラーからだとchromeが共有ライブラリに見えて実行できなかったけど、普通にコマンドラインから起動すれば問題なかった。
  3. chromiumの情報を確認する。"chrome://version/"というURLを打つと色々情報が見れる。そこにDartVMという項目が出てればちゃんとDartiumが動いてるはず。自分が落としてきたのは下の表示だった。

コード書く

こことかこことかこの辺を参考にしながら書いてみた。コツがつかめれば実際のリファレンスの方をみるといいかも。特にdart:htmlのElementは良く読むべし。

<!doctype html>
<head>
    <meta charset="utf-8">
    <title>dartlang test</title>

    <link rel="stylesheet" type="text/css" href="./css/bootstrap.min.css" />
    <link rel="stylesheet" type="text/css" href="./css/client.css" />
</head>
<body>
    <script type="application/dart">
    #import("dart:html");
    void main() {
        var msg = "Testing dart!";
        window.alert(msg);
        print(msg);
        Element btn = document.query('#button');
        btn.on.click.add((Event e) {
            Element stage = document.query('#stage');
            stage.innerHTML = "OMGOMGOMGOMG";
        });
    }
    </script>
    <div class="topbar">
        <div class="fill">
            <div class="container">
                <a class="brand" href="#">dartlang test</a>
            </div>
        </div>
    </div>
    
    <div class="container">
        <div id="stage">AAAAAAAAAAAAAAAAAAAA</div>
        <div id="button">Click ME!</div>
    </div>
</body>
</html>

やってることはわりと単純。

  1. window.alertでアラートウィンドウにメッセージを出力する
  2. printでデバッグメッセージを出力する
  3. buttonというidを持つ要素のクリックハンドラでstageというidを持つ要素の中身を書きかえる

コードを埋めこんだhtmlができたら、あとは普通にDartiumで表示する。もし構文に何らかのエラーがあった場合はデベロッパーツールのConsoleに表示されるから見るといい。画像は上がクリック前、下がクリック後。

型チェック

ドキュメントにも書いてあるけど、Dartiumの実行時に環境変数を設定してやるとDartの型チェック機能を有効化できる

$ DART_FLAGS='--enable_type_checks --enable_asserts' ../lib/dartium-lucid64-inc-4350.4350/chrome &

この状態で意図的に型エラーが出るソースに書きかえた(Event eのところをint eにした)ところ、ちゃんとエラーが検出された。

感想

  • バイナリのおかげでかなり簡単にDartが試せる。
  • Consoleにエラーが出るからデバッグはまあまあやりやすい。まあこれはjavascriptでも一緒だけど。
  • 型チェックは常にONでもいいかも。

2012年2月11日土曜日

Trying "dart:io" with dart

ブラウザ側の処理を書くためちょっと試しにjavascriptを書こうとしてたものの、そのあまりに気持ちわるい言語仕様に挫折してしまった。Coffee Scriptも試したけどやっぱり文法が受けつけなかったので、早々にdartを試してみることにした。

dart:io

dartはブラウザの中だけじゃなく普通のスクリプト言語的にも実行できるようなので、ちょっと前に追加されたというioライブラリでちょろっと書いてみた。環境の構築方法はこの辺を参照。APIリファレンスを見るかぎり〜Syncていう同期的に実行する関数と非同期的に実行する関数の二種類があるみたい。Sync無しの関数がデフォルトっぽいので、ひとまず全て非同期で書いてる。

#import('dart:io');

void onOpen(RandomAccessFile opened) {
    opened.closeHandler = () => print("Closed stream");
    opened.errorHandler = (err) => print(err);
    opened.noPendingWriteHandler = () => opened.close();
    print("Writing to stream");
    opened.writeString("aardvark\n");
}

void main() {
    var name = "./aardvark";
    var f = new File(name);
    print("Attempting to write "+f.name);
    f.existsHandler = (bool b) {
        if (b) {
            print(f.name+" already exists!");
        } else {
            print(f.name + " doesn't exist...");
            f.openHandler = onOpen;
            f.open(FileMode.WRITE);
        }
    };
    f.exists();
}

"=>"記号で関数を定義するのはC#で慣れてるからどってことない。それより全て非同期で書こうとするとハンドラーだらけで変な感じ。

実行結果

$ dart --enable_type_checks test.dart
Attempting to write ./aardvark
./aardvark doesn't exist...
Writing to stream
Closed stream
$ dart --enable_type_checks test.dart
Attempting to write ./aardvark
./aardvark already exists!
$ cat aardvark 
aardvark
$ 

実行結果は期待通り。ついでに"--enable_type_checks"オプション型チェックを試してみた(existsHandlerの引数をbool bじゃなくてint bにしてみた)ところ、下のような結果になった。

$ dart --enable_type_checks test.dart
Attempting to write ./aardvark
Unhandled exception:
'dart:io': Failed type check: line 1888 pos 31: type '(int) => Dynamic' is not assignable to type '(bool) => void' of 'handler'.
 0. Function: '_File@14117cc4.set:existsHandler' url: 'dart:io' line:1888 col:31
 1. Function: '::main' url: '/home/masato/dart/test/test.dart' line:15 col:21

"--enable_type_checks"無しで実行すると何も言わないので、一応ちゃんと動いてるみたい。

まとめ

javascriptよりはるかにまっとうな言語なので早く普及してください。ちなみにsynonym.dartlang.orgにはjavascriptとdartを比較した色々な例が出てて、javascriptがいかに気持ち悪い言語かが良くわかる。