Proposalはここ
wycats/javascript-decorators
stage: 1 なのでまだまだ仕様変わる可能性あり。
typescriptやbabelでつかいたい場合は次のようなコンパイラオプションが必要
tsc --experimentalDecorators
babel --stage=1
デコレータの振る舞い
- クラスデコレータは Class Functionをとって Class Function を返す関数
- クラスの関数デコレータは instance とname と descriptor を引数に descriptorを返す関数
- 引数を取るデコレータは、関数を返す関数として実装する(部分適用っぽい感じ)
- クラスに所属しない関数へのデコレータはbabel/typescriptともに未実装?
使ってみた感想
- 元の振る舞いを変えずに参照透過を維持するような関数が見通しが良い
- 用途は以下が考えられる
- DI
- Logging
- Memoize
- Mixin
- アノテーション付与
- テスト用ヘルパ
結局Javaやpythonと同じような用途になると思う。
変わった用途として、pythonのflaskとかはAPI ルーティングで使ってたっけ。
@get('/')
def index():
...
こんなやつ
Debug Decorator
作ってみた。関数の振る舞いを変えずに入出力をconsole.log で吐くデコレータ。
npm install @mizchi/debug-decorator で入る
const debug = require('@mizchi/debug-decorator');
class X {
@debug
get name() {
return 'xxx';
}
@debug
square(n: number){
return n * n;
}
}
const x = new X;
x.name;
const r = x.square(2);
こんなログが出る。
debug: get name - xxx
debug: call square
[args] 2
[return] 4
それぞれにdescriptorがあるかどうか判定してオリジナルのものをラップした関数に書き換えている。
コードはこんな感じ
module.exports = function debug(target, name, descriptor) {
if (descriptor.value) {
const original = descriptor.value.bind(target);
descriptor.value = (...args) => {
const val = original(...args);
console.log(`debug: call ${name}\n\t[args]`, ...args, `\n\t[return] ${val}`)
return val;
}
}
if (descriptor.get) {
const originalGet = descriptor.get.bind(target);
descriptor.get = () => {
const val = originalGet();
console.log(`debug: get ${name} - ${val}`);
return val;
}
}
if (descriptor.set) {
const originalSet = descriptor.set.bind(target);
descriptor.set = (val) => {
const r = originalSet(val);
console.log(`debug: set ${name} - ${val}`);
return r;
}
}
return descriptor;
}
Dependency Injection
Decorator, たしか元々TypeScript with AngularにDIを、みたいな話から始まってた話、とりあえず作ってみた
npm install @mizchi/injector で入る
サンプルコード
const createInjector = require('@mizchi/injector');
const rootInjector = createInjector();
const injector = rootInjector.createChildInjector(); // can search parents
class A {}
class B {}
injector.mapSingleton(A);
injector.mapValue(B, new B);
// injector.unmap(B); // to remove
@injector({a: A, b: B})
class T {
a: A;
b: B;
}
const t = new T();
assert.ok(t.a instanceof A);
assert.ok(t.b instanceof B);
T に対して injectorがgetterを定義することでメンバアクセス時にDIから参照を解決する。
自分が唯一業務で使ったことがあるDIの Robotlegs AS3 Micro-Architecture - index.html を参考にした。
これもうちょっとちゃんと設計しないと治安が悪くなりそうなので、もっと良いAPIを考える。