Hatena::ブログ(Diary)

mizchi log

@mizchiの雑記帳

2013-07-19

JavaScriptで型が書けるDSLを提供するdeftypes.js作った



こじらせJavaScriptシリーズです。
mizchi/deftypes.js https://github.com/mizchi/deftypes.js

主にcoffee-script用のDSLです。以下すべてcoffee。

ブラウザ

<script src="https://raw.github.com/mizchi/deftypes.js/master/deftypes.js"></script>

Deftypes(); //provide DSL

Node

npm install deftypes

概要


型が書けます。残念ながら動的チェックです。

Point = {x: Number, y: Number}
p1 = def Point, {x:1, y:2} #=> {x: 1, y:2}
p2 = def Point, {x:1, z:2} #=> type error

defとTがDSLです。オプションで何もせずにインスタンスを返却するだけするモードもあります。これによってパフォーマンスを落とすことなく、型を明示的に宣言しながら開発することができます。(その際のオーバーヘッドは型情報の宣言のみで、ほぼ無視出来るでしょう)

Nullable型

NullableNumber = {n: T.Nullable(Number)}
p1 = def NullableNumber, n:1
p2 = def NullableNumber, n:null

配列

number_list = def [Number], [1,2,3]

関数型

f1 = def T.Func([Number, Number], String), (m, n) -> "#{m}, #{n}"
f1(1,2) #=> "1, 2"
f1("",2) #=> argument error

引数と返り値の型を動的にチェックします。違反が検出されたら例外をthrowします。


型宣言されたインスタンスを変更するとき、専用の高階関数の中で副作用をだすようにすれば、高階関数を抜けた時にも型チェックをすることができます。

p = def Point, {x:1, y:2}
def Point, p, ->
  @x = 3

# Type Error
def Point, p, -> @y = "not number"

やや面倒ですが、副作用を出すという危険な行為は面倒臭くしておきたいのです。
これを無視してブロック外で行われた変更は検出しません。実装上は検出できるけど、このライブラリ専用の挙動が増えすぎるのでまだ避けてます。


それ以上の詳しくはREADMEとテストコードをみてください。


用途


静的解析ではないので、テストコードを書いたりしてカバレッジあげないとあまり意味がありません。むしろテストコードでの型チェックに使うといいかもしれません。
型をコード中に定義して文芸的プログラミングな文脈で可読性をあげる目的もあります。これは個人的な目的です。

TODO


typescriptのd.tsから型情報を読み込めるようにする
実装例としてテトリスか何かをつくる
可能ならば型推論する(難度が高い…)

2012-12-09

非同期呼び出しの見た目上の同期を実現するためにnode-fibers 使ってみた


laverdet/node-fibers · GitHub https://github.com/laverdet/node-fibers
ネタ元としてはRubyのFiber。

他のcontrol flow系とは経路が違って、V8を直接叩いて実装されている。よって普通のJSの直感から反した挙動を取ることが可能となっているし、nodeでしか動かない。

インストール


いつもの

npm install fibers

使い方

sleepの公式サンプル

var Fiber = require('fibers');
function sleep(ms) {
    var fiber = Fiber.current;
    setTimeout(function() {
        fiber.run();
    }, ms);
    Fiber.yield();
}

Fiber(function() {
    console.log('wait... ' + new Date);
    sleep(1000);
    console.log('ok... ' + new Date);
}).run();
console.log('back in main');

Fiberブロックの中が見た目上同期している。

特徴

他のcontrol flow系ライブラリの when ~ then ~ といったコールバックをキャッチする煩わしさから解放される。かわりに同期的に呼び出したいプロセスをFiberで囲う必要がある。

Fiber.yield が呼ばれると Fiber.current の run が呼ばれるまでは次の処理を止める。

オレオレスニペット


これぐらいラップしてしまうと意識せず使える(気がした)

Fiber = require('fibers')
Block = (main) ->
  sync = (f) ->
    fiber = Fiber.current
    f (-> fiber.run())
    Fiber.yield()
  Fiber(-> main(sync)).run()

Block (sync) ->
  console.log '1'
  sync (done) ->
    setTimeout (-> console.log '2'; done()) , 300
  sync (done) ->
    setTimeout (-> console.log '3'; done()) , 300
  console.log '4'

1,2,3,4と順番に出力される
非同期ならば 1,4, (2 or 3) となるはず

2012-11-19

should.jsが辛いのでやめたくなった


Rspec風に使える、という理由で使ったけど、もうなんというか気分的に辛い
visionmedia/should.js https://github.com/visionmedia/should.js/

問題1 nodeのassert依存


ブラウザ用に移植できない

問題2 undefined, null は prototypeを持たない


次のようなコードは getHogeがundefined返してしまうと hoge.shouldを触った時点で落ちる

hoge = getHoge()
hoge.should.equal 'hoge'

アサーションにすらたどり着けず落ちるのはストレスたまる
やっぱラップするタイプのexpectの方がよさそう

問題3 nodeのネイティブモジュールに依存したオブジェクトはprototypeを共有していない


JSDOMで生成したオブジェクトのアサーションができない!!!

結論


chai使いましょう

Home - Chai http://chaijs.com/