May 2015 Archives

gjsでGIRepositoryを操作する

| No Comments | No TrackBacks

先日のエントリからの調べ物として、まずgjsでgobject-introspectionを使用する方法を残しておく。gjsはgobject-introspectionで実現していて、gobject-introspectionはそれ自身がgobject-introspectionをサポートしているので、gjsを使えば簡単なメタプログラミングが実現できるというわけだ。

ローカルでgobject-introspectionをサポートするAPIにどのようなものがあるかは、インストール先のgir-1.0フォルダを探して見てみると良い。典型的なLinux distroの場合は /usr/share/gir-1.0 などにあるだろう。

gjsのgirバインディングは素直だ。使いたいモジュールは imports.gi.XXX でアクセスできる。

var GIR = imports.gi.GIRepository;

GIRネームスペースには、(ドキュメントを読んでもらえればわかるが)GIRepositoryというクラスが存在する。gjsからアクセスする場合は、共通の型名プレフィックスである GI を除いた Repository がクラス名となる(共通のプレフィックスが何になるかは、*.girファイルに記述してあるので、gjsは機械的に名前を計算できる)。

girベースのAPIには、classが定義されており、その中にはinstanceメンバーとstaticメンバーが存在する。instanceメンバーの場合、*.girファイルには関数の定義部分に instance-parameter というXML elementが存在している。それ以外はstaticメンバーとなる。

staticメンバーにアクセスする場合は、クラスをあらわすオブジェクトのメンバーとして呼びだせば良い。Repositoryには、 girepositoryget_default() という、デフォルトのRepositoryを返す関数がある。girのclassにはsymbol-prefixというものが定義されており、これは関数名の解決で使用される。同様に、GIRのネームスペースには、ネームスペースのプレフィックスも規定されている。GIRepositoryネームスペースのプレフィックスは "g" で、Repositoryクラスのプレフィックスは "irepository" だ。

ここから、girepositorygetdefault()という関数は、GIRepositoryネームスペースのRepositoryクラスのgetdefault()というメソッドとして、定義されることになる。たとえば、get_default()の呼び出しはこうなる:

var GIR = imports.gi.GIRepository;
var repo = GIR.Repository.get_default();

これでrepo変数にRepository型のオブジェクトが格納されたことになる。

gjs> repo
[object instance proxy GIName:GIRepository.Repository jsobj@0x7f8cbe1357f0 native@0x21d6420]

インスタンスメソッドのマッピングも簡単だ。名前の解決はstatic関数と同様に行われる。引数については、前述の通り、インスタンスメソッドの定義には instance-parameter というXML elementが含まれている。この引数は、gjsのAPIではthisの扱いになり、引数とはならない。

girepositorygetloadednamespaces()関数を呼び出してみよう。この関数もRepositoryクラス内で定義されている。唯一のパラメーターが instance-parameter なので、gjsでインスタンスメソッドとなった時には、引数は存在しない。

gjs> repo.get_loaded_namespaces()
GLib,GObject,GIRepository

girepositorygetninfos() は、2つ引数をとり、最初はインスタンス パラメーターであり、最後はネームスペース名となる。戻り値には、そのネームスペースに含まれる「定義」の数を返す。

gjs> repo.get_n_infos("GIRepository");
152

それぞれの要素は girepositoryget_info() で取得できる。

gjs> var info = repo.get_info("GIRepository",0);
gjs> info
[boxed instance proxy GIName:GIRepository.BaseInfo jsobj@0x7f05f0735a60 native@0x715320]

gjsを使ったGIRepositoryの基本的な操作は、概ねこの辺りが分かれば出来そうだ。

途方もない空白期間の後にしれっと書いているけど、ここはもともとメモ置き場だし、まとまった内容はだいたい他の場所に書いているので問題ない。ここに書いているのは、要するにまとまっていないアイディアである。

そんなわけで今日はTypeScriptをGNOME開発で使うにはどうするのがいいか、について考えてみる。

現在のGNOME開発のトレンドは、主にJavaScriptを使うということになっているようだけど、実態はC + 任意の言語 ということらしい。その心は、Cおよび好きな言語でアプリを書けるようにしておけ、ということだそうだ。Cをサポートするのは、GNOMEの基本的な方針ということなのだろう。

古いGNOMEのモデルだと、任意の言語、の部分は、それぞれの言語のバインディングが担うことになるわけだけど、2010年代のわれわれはそれが各言語バインディングごとに追いついたり追いつかなかったりで、理想的には機能しない、ということを知っている。

その問題を解決するために、GNOMEにはgobject-introspectionというAPI公開仕様が導入された。gobject-introspectionをサポートしている言語ランタイムでは、使いたいGNOMEのライブラリがgobject-introspectionに準拠していれば、特別にその言語バインディングを作成する必要はない。

さて、現在のGNOME 3.xでは、JavaScriptが標準としてプッシュされている。gnome-shellなどはJavaScriptで拡張を書くことが出来る。GNOMEのJavaScript実行環境には、spidermonkeyベースのgjsとwebkitベースのseedという実装が存在していて、どちらもgobject-introspectionに準拠している。これらのJSランタイム実装は、gobeject-introspectionを活用して、API情報をクエリして、JSオブジェクトに対するメソッド呼び出しを適宜解決して、そのgobjectに対するネイティブ呼び出しを実現する、というわけだ。これは少なくとも使い方としてはORBitだのBonoboだのと言っていた時代よりははるかに簡単だ。

さて、gjs(seedがBlinkで使えるかどうかは知らないが、Apple専用となったWebKitを使う気は全く無いので、以降gjsについてのみ扱う)でgobject-introspectionのgirを経由してAPIを「使える」ことは分かった。一方で、プログラムの開発時には、JavaScriptでは型情報が無いので、静的型付け言語を使うことで、コード補完などを実現できるようにしたい。

というわけで、TypeScriptを使いたい。TypeScriptは、JavaScriptにコーディング時の型情報を提供するものだ。TypeScriptはどうやって型情報を取得しているかというと、バージョン1.5の現在では、主に型情報を定義したTypeScript (d.ts)を参照して、型情報を構築している。AMD (asynchronous module definition)を利用することも可能だ。

(実行時には型情報は無いわけだが、gobject-introspectionのAPIで何とかなるのではないかとは思う。gjsが実現しているからといって、その仕組みが開発時に、すなわちTypeScriptで、そのまま使いまわせるわけではない。)

というわけで、現在の仕様に準拠するかたちで、GNOME開発でTypeScriptを使いたい場合は、このd.tsファイルをgirファイルから構築してしまえば良いわけだ。

しかし、これはあまり理想的ではない。これでは、girが変換されてd.tsが提供されたAPI以外については、「バインディングが提供されていないAPIはサポートされない」状況に近い。現実的な問題としては、DefinitelyTypedに誰かが登録したd.tsに対応するgirは、d.tsの利用者(プログラムの開発者)の手元にある各ライブラリと、バージョンが合わないかもしれない。出来ることなら、誰かがわざわざAPIを変換しなくても、girのリポジトリを、直接、型情報リポジトリとして使えるようにしたい。

これを実現するためには、typescriptコンパイラそのもの(tscおよびそのLanguageService)に手を入れなければならないだろう。しかし、typescriptそのものにプラットフォーム固有のgobject-introspectionのサポートを組み込むというのは、やはり抵抗感がある。tscはValaのようなGNOME-centric languageではない。

おそらく、ここで最も望ましいアプローチは、F#のType Providerのような仕組みなのではないか。TypeScriptの場合、実行時には型情報は存在しない。コンパイル時に型情報を解決出来れば良い。F#ではopenキーワードを拡張することで、Type Providerの仕組みを実現した。同様のことが、TypeScriptでも実現できるのではないか。

実のところ、JavaScriptライブラリにAPI情報を提供する仕組みは、他の実行環境等にも存在していて、「型情報をxxxから取得できるようにしろ」という要求にはissuesやpull requestsの前例がある。

  • JSDocからのAPI情報取得 https://github.com/Microsoft/TypeScript/pull/2646
  • node.jsのnode_modulesからの情報取得 https://github.com/Microsoft/TypeScript/issues/247

AMDサポートも、この(既に実現している)一種と言える。

また、QMetaObjectのようなものを(gobject-introspectionとsmokeを経由せずに)サポートしようと思ったら、やはり独自のtype providerがあれば実現できる。

これらのような例を考えるに、おそらくそれぞれを個別にサポートするよりは、何らかのかたちで標準化されたtype providerをサポートするようにした方が望ましいのではないか。

node_modulesサポートのissueは、TypeScript 2.0というカテゴリでまとめられているので、この辺りの議論は、もしかしたら早めに出したほうが良いのかもしれない。個人的にTypeScriptにもGNOME開発にも全力でコミットするつもりがないので、tscパッチなど何かしらの具体的な実装を伴わないアイディアで終わってしまっているのが残念なところではある。気が向いたら実装に着手するかもしれない。