2016-07-11T22:00:27+09:00

ModuleLoader の仕組み

ModuleLoader は AppleScript のライブラリシステムです。OS X 10.9 (2013年) で、ようやく AppleScript にライブラリシステムが標準搭載されましたが(AppleScript Libraries)、それから先駆けて3年以上前からほぼ同じことをできる実用的なシステムを確立していました。当然、OSX 10.9 以前のバージョンでも動作します。紆余曲折ありましたが、現在の仕組みは 2010-01-12 に公開したバージョン 2.1 で確立しました。

AppleScript Libraries が登場したので、お役目ご免かといえば、そうでもないと考えています。AppleScript Libraries には真似できない幾つかのことができます。

ModuleLoader と AppleScript Libraries では、後述するように、やっていることはよく似ていると思われます。今更ですが、ModuleLoader の仕組みを解説することにも意味があるのではと考えています。

ModuleLoader の基本構文

まずは、ModuleLoader を使ってのライブラリを読み込む基本的な構文です。

property Lib1 : module
property _ : boot (module loader) for me

Lib1's do_something()

property で読み込むライブラリを指定します。property の 値として module コマンドを設定すると、その property に ライブラリを読み込むんだという印 (module specifier) がつきます。ライブラリの名称を指定することもできますが、省略すれば property 名と同じ名前のライブラリを探します。

次の「boot (module loader) for me」という文が実行された段階で、ライブラリの読み込みが行われます。このコマンドを上の例のように property の値として設定すると、property の値はコンパイル時に評価されますので、ライブラリの読み込みはコンパイル時に行われます。

実行の度に、最新のライブラリを読み込ませたい場合は、run ハンドラの中で実行すれば良いです。

ライブラリを読み込む仕組み

boot (module loader) for me」の動作を詳細に解説します。

  1. まず、module loader コマンドはスクリプティング機能追加 ModuleLoader.osax に定義されているコマンドです。
    • 実行すると、loder スクリプトと呼ぶスクリプトオブジェクトを返します。loader スクリプトと ModuleLoader.osax が協調して動作することが ModuleLoader の肝です。
  2. boot コマンドは、loader スクリプトで定義されているハンドラで、for ラベルに渡されたスクリプトオブジェクトにライブラリをセットアップします。
  3. for ハンドラに渡されたスクリプトオブジェクトから、ModuleLoader.osax が提供している extract dependencies from コマンド を使って、module specifier が設定された property 名 をリストアップします。
    • スクリプトオブジェクトの property のリストを得ることは AppleScript だけでは行えず、C 言語レベルの API を使う必要があるので、スクリプティング機能追加が必要になります。
  4. extract dependencies from で確認されたライブラリをロードします。ロードしたライブラリは、loader スクリプトにキャッシュします。
    • ライブラリの検索は、スクリプティング機能追加で定義されたコマンドで高速に行われます。
  5. ロードしたライブラリを property に設定する。
    • この際に、ライブラリを設定する property を文字列で指定する必要がありますが、XAccessor と同じテクニックを用いています。
    • XAccessor を使えば、スクリプトオブジェクトの property 名やレコードのラベルを文字列で、すなわち変数として扱うことができます。
  6. ロードしたライブラリに対しても、上記の方法を再帰的に適用して、ライブラリのライブラリのロード及び property の設定を行います。

なぜ loader スクリプトが必要なのか

上記のようなライブラリをロードする仕組みは、もしかしたら冗長に感じるかもしれません。例えば、次のように property の宣言でダイレクトにライブラリを読み込んでしまえ、と思うかもしれません。

このような使い方も ModuleLoader.osax はできます。でも、この使い方はお勧めできません。なぜなら、Lib11 と Lib12 には Lib1.scpt が重複してロードされてしまうからです。同じ Lib1.scpt からロードされているにもかかわらず、Lib11 と Lib12 には独立したスクリプトオブジェクトが設定されます。

上の例のように、一つのスクリプトの中一つのライブラリをわざわざ別の property に読みことはないと思われます。でも、複数のライブラリを読み込んだ時、それぞれが共通のサブライブラリを要求したとします。読み込んだライブラリをキャッシュして一元管理する場所がないと、サブライブラリが重複して読み込まれてしまいます。

loader スクリプトを経由させることで、トップレベルのスクリプト及び読み込んだライブラリのあらゆる場所で、ライブラリの同一性が確保することができます。

ライブラリをリロードする仕組み

先に解説したライブラリをロードする仕組みだけだと、いろいろ問題が発生します。まず、どんな問題が発生するのか解説したのち、それをどのように解決しているのか解説します。

まず一つ目の問題は、上の仕組みだけだとライブラリを一度しか読み込むことができなくなります。例えば、「boot (module loader) for me」 文を run ハンドラの中で実行するようにして、スクリプトが起動されるたびにライブラリの読み込みが行われて欲しいと考えます。しかし、property にライブラリが設定されると、その property はスクリプトの終了ともに保存されます。二回目の実行時に boot (module loader) for me 文 が実行された時、 property には module specifier ではなく前回の実行時にライブラリとして読み込んだスクリプトオブジェクトが居座っていますから、ライブラリの読み出しが行われないことになります。

ライブラリのライブラリのロードにも問題が発生します。テストコードを含んだライブラリを開発したとします。テストコードの実行(ライブラリを単体のスクリプトとして実行する)を行うと、ライブラリが依存しているサブライブラリの読み込みが行われて、property に設定しされた module specifer がスクリプトオブジェクトにすり替えられます。そして、そのまま保存してしまったとします。この場合、サブライブラリを読み込んでしまったライブラリをロードするとサブライブラリの読み込みは行われないことになります。

でも、安心してください。ModuleLoader には上記の問題を解決する仕組みがあります。もちろん、ユーザーは何も意識する必要はありません。

loader スクリプトは property へライブラリの設定を行うとともに、__module_dependencies__ というglobal 変数の中に、読み込んだライブラリの情報及び設定した property 名を記録します。global 変数はトップレベルのスクリプトの property と同じ意味です。普通、__module_dependencies__ という property は定義さてれていないでしょうから、新しい property が追加されることになります。

一回目のライブラリの読み込みと同時に、propery に設定されていた module specifier の情報は、__module_dependencies__ に移されます。二回目のライブラリのリロード時は property ではなく__module_dependencies__ からライブラリに関する情報を取得するように動作します。

このように、ModuleLoader はユーザーが意図したようにライブラリのロードが行えます。

ModuleLoader と AppleScript Libraries の類似性

OS X 10.9 から、標準搭載の AppleScript のライブラリシステム、通称 AppleScript Libraries は言語を拡張していますから、スクリプティング機能追加では真似できないところがあります。しかし、本質的なところは非常によく似ていると感じます。特に、use 構文を使った場合は瓜二つです。use 構文でライブラリを指定すると、property の定義と同値になります。

また、use 構文を使うと、required import items という隠しプロパティが生成され、use 構文で指定した項目が格納されています。ModuleLoader がプロパティ __module_dependencies__ を生成することと、とてもよく似ています。

AppleScript Libraries ModuleLoader
基本構文
use Lib1 : script "Lib1"
property Lib1 : module
boot (module loader) for me
隠しプロパティ
required import items
-- {{item:script "Lib1" of «script»}}
__module_dependencies__
-- {{class:dependency info, name:"Lib1", module specifier:{class:module specifier, name:"Lib1"}}}

AppleScript Libraries では、ModuleLoader の「boot (module loader) for me」に相当する部分は、当たり前かもしれませんがないですね。ModuleLoader は言語の拡張ではないので、モジュールの読み込みを明示的に実行しなければなりません。これを煩わしいという見方もできますが、ユーザー側でライブラリの読み込みタイミングを指定できる、もしくは明示的にリロードできるなど、自由度があるという捉え方だってできます。というか、そう捉えてください。

ModuleLoader で AppleScript Library をロードする

ほとんどの用途では、AppleScript Libraries を素直に使えば足りるのだろうと思います。しかし、ModuleLoader を使えば、コンパイル時にすべてのライブラリのロードを済ましてしまうということができます。そうすれば、ライブラリが用意されていない他の Mac に持って行っても動作させることができます。

そこで、ModuleLoader に AppleScript Libraries の構文を解釈できるように拡張してみました。つまり、従来の ModuleLoader のライブラリ指定方法である「property Lib1 : module」の代わりに、 「use Lib1 : script "Lib1"」 を使えるようにしてみました。

use scripting additions
use Lib1 : script "Lib1"
property _ : boot (module loader) for me

パッケージの中の Script Librareis フォルダにライブラリを配置するという方法もありますが、 ModuleLoader で読み込ませてしまう方が簡単です。だいたい、パッケージ内にもれなくライブラリをコピーできたか、確認するのは難しそうです。

まだ、ドキュメントが整備されていませんが、お試していただけると嬉しいです。

ModuleLoader 2.3.4 からの変更点は次の二つです。

機能的には、二つだけですが内部の実装は非推奨は古い API を置き換えて徹底的に近代化しています。長くメンテナンスできるコードベースになっていると思います。

すでに十分に安定して使えます。Pelease check it out !

自分の公開しているソフトは皆そうですが、ModuleLoader のソースコードは公開しています。気に入らないことがあったら、自分で修正してみてください。ソースコードでわからないことがあったら質問していただいても構いません。できる限りお付き合いします。