ModuleLoader.osax の真価

load script を置き換える」に続き、ModuleLoader.osax のことなどを。ModuleLoader.osax の機能をOSAX としてではなく、Script Editor のプラグインとして利用できないのかなぁ...などと妄想する日々。この記事に関して Script factory さんが補足記事を書いてくれています。ModuleLoader.osax の理解が深まると思いますので参照してみてください。

ところで、Windows で自動化を行うソフトとして WinMacro なるものが紹介されていますね。この記事を読んで...QuicKeys

よくよく考えてみたらこういう OS 全体の操作を記録、操作するようなソフトウェアって Mac に古くからありますね。いまなら Automator でしょうか。AppleScript は記録がろくに動かなくなってしまいましたが...。

まぁ、Windows のことはよくわからないので OS の自動化ソリューションにどのようなものがあり、どれくらいの需要があるのかは知らないのですが。

さて。

まずは、お詫びです。「load script を置き換える」で次のスクリプトが動かなかった件ですが...ModuleLoader.osax のバージョンが古い為に起こったエラーでした。

on run
    -- /Library/Scripts 以下から AppleScript Help.scpt を検索
    -- additional paths オプションで検索パスを指定
    -- 検索パスを other paths オプションで additional paths で指定したパスだけに制限
    set modules_folder to path to scripts folder from local domain
    find module "AppleScript Help.scpt" additional paths ¬
        {modules_folder} without other paths
end run

最新版(2.2.1)にしたら動きました。...すいませんでした。お手数をおかけいたしました。

load script を置き換える」では ModuleLoader.osax の基本的な機能だけを使ってみました。しかし、ModuleLoader.osax が真価を発揮するのはここからです。

次のスクリプトを Value.scpt として ModuleLoader.osax の検索パスの通ったフォルダに保存しておきます。単純な値を保持しておくだけのスクリプトオブジェクトです。

Script Editor で開く

property _value : missing value

on set_value(val)
    if val is _value of me then return
    set _value of me to val
end set_value

on get_value()
    return _value of me
end get_value

そして、次のスクリプトを Value Wrapper.scpt として保存します。

Script Editor で開く

property value_object : module "Value"

on get_value()
    return value_object of me
end get_value

on set_value(val)
    if value_object's get_value() is val then return
    value_object's set_value(val)
end set_value

on _log()
    log (value_object's get_value())
end _log

たいして意味のないスクリプトです。Value.scpt をラップしただけで、メソッドは Value.scpt のメソッドを呼び出すだけのものです。最後にクライアント。次のスクリプトを Client.scpt としてデスクトップにでも保存しておきます。

Script Editor で開く

property value_object : module "Value"
property wrapper : module "Value Wrapper"
property loader : boot (module loader) for me

on run
    tell value_object
        log get_value()
        --> missing value
        set_value("Hello, world")
    end tell

    tell wrapper
        _log()
        --> "Hello,world"
        set_value("Hello, ModuleLoader")
    end tell

    tell value_object to log (get_value())
    --> "Hello, ModuleLoader"

    log (value_object is wrapper's get_value())
    --> true
end run

Value.scpt と Value Wrapper.scpt を読み込み、それぞれのメソッドを呼び出しています。このスクリプトのポイントとして次の点が上げられます。

  • 依存しているモジュールが常に最新の状態に保たれる
  • 同一のスクリプトから読み込まれた複数のスクリプトオブジェクトが同一のオブジェクトを参照している

これらは前回の load module では行えなかったことです。細かく見てみましょう。

Value.scpt は単純なスクリプトです。Value Wrapper.scpt は Value.scpt を読み込み、それを操作するスクリプトです。Client.scpt は Value.scpt を読み込み、直接それを操作し、同時に ValueWrapper.scpt も読み込み、オブジェクトを介して Value.scpt から読み込んだオブジェクトを操作します。

スクリプトの関係

load script でそれぞれのスクリプトをこのように読み込んだ場合、Value Wrapper.scpt で参照している value_object と Client.scpt で参照している value_object は異なったオブジェクトになります。しかし、ModuleLoader.osax を使って読み込んだ場合、両者の value_object は同じオブジェクトを参照します。

また、Value.scpt を再編集した場合、従来なら Value Wrapper.scpt、Client.scpt 共に再コンパイルしないと Value.scpt の変更は反映(再読み込み)されませんでしたが、ModuleLoader.osax は再コンパイルをせずとも最新の状態を反映してくれます。

これは、property の仕様(オブジェクトの属性、状態を記憶しておく)を覆すような動作ですので、問題といえば問題なのですが...。

AppleScript は基本的にイントロスペクションが弱い。スクリプトの内部からスクリプトについて調べることができません。例えば、グローバル変数の一覧を取得したり、スクリプトオブジェクト、ハンドラについて調べることもできません。この辺りのことができれば少しは違うのですが、現状ではスクリプトから AppleScript の内部にまで手を入れることができません。property の仕様を変更することでしか他の言語にあるようなモジュールの読み込みを実現できない...というのが AppleScript の問題点なのかもしれません。

まぁ...そういうことは置いておき、複数のスクリプトオブジェクトが同じオブジェクトを参照する...ということなら ModuleLoader.osax を使わずに行うことは可能です。方法は 2 通りあります。

  1. オブジェクトの参照をオブジェクトに渡す(オブジェクトコンポジション)
  2. オブジェクトをシングルトンとして設計する

オブジェクトコンポジションは問題なく行えますし、シングルトンは...厳密にはシングルトンにはできませんが、似たようなことはできます。どうするかは...またの機会にでも。

いずれにしても、どの方法を利用するかは作るスクリプト次第です。load script を用いるか、スクリプトバンドルと load script を組み合わせるか、ModuleLoader.osax を利用するか...。選択肢が増えることはいいことです。

スクリプトを見ていきます。

property value_object : module "Value"

この部分は property で指定した変数に(ここでは value_object)モジュールを読み込む指定を行っています。つまり、まだモジュールは読み込まれていないのです。モジュールが実際に読み込まれるのは boot 命令が評価されたときです。

property loader : boot (module loader) for me

まず、module loader 命令がスクリプトオブジェクトを読み込むスクリプトオブジェクトを生成します。次に boot 命令で module loader が返すオブジェクトを使ってスクリプトを読み込み、かつ読み込んだスクリプトオブジェクトが依存しているスクリプトを読み込みます。

つまり、boot 命令が評価されたときに Client.scpt は自身の属性 value_object と wrapper にスクリプトを読み込み、同時に Value Wrapper.scpt の属性 value_object にもスクリプトを読み込んでいるのです。

では、Value Wrapper.scpt は単体では動かせないのかというと...その通りです。

property value_object : module "Value"

この時点ではまだスクリプトは読み込まれていないため、Value.scpt のメソッドを実行することはできません。もちろん、単体でテストを行いたいときはあるでしょう。そういうときは run ハンドラなどで boot 命令を使用します。

Script Editor で開く

property value_object : module "Value"

on get_value()
    return value_object of me
end get_value

on set_value(val)
    if value_object's get_value() is val then return
    value_object's set_value(val)
end set_value

on _log()
    log (value_object's get_value())
end _log

on run
    -- Value Wrapper のテストを行うため、モジュールを読み込む
    boot (module loader) for me
    _log()
    --> missing value
    set_value("Hello, world")
    _log()
    --> Hello, world
end run

ところで... module loader 命令が返すスクリプトオブジェクトとはいったいなんなのでしょうか?

結論から書くと、ModuleLoader.osax が入っていたディスクイメージ内にある loader.applescript なのでした。だから loader.applescript のメソッドを呼び出そうと思えば呼び出せます。それがいいことかどうかはともかく。

ModuleLoader.osax をいろいろと触っていてふと、思ったのですが...同一のスクリプトファイルから個別のスクリプトオブジェクトを読み込むことはできないのでしょうか?

先にも見たように ModuleLoader.osax の boot 命令を使って同一のスクリプトファイルから読み込むと、依存しているスクリプト間では同一のスクリプトオブジェクトを参照するのでした。では、そうしたくないときは?

Script Editor で開く

property object_A : module "Value"
property object_B : module "Value"
property loader : boot (module loader) for me

on run
    object_A is object_B
--> true
end run

これだと同じスクリプトオブジェクトを参照してしまいます。load module 命令を使うのかな。

Script Editor で開く

property object_A : load module "Value"
property object_B : load module "Value"

on run
    object_A is object_B
    --> false
end run

しかし、これだと ModuleLoader.osax のモジュールが依存しているモジュールの自動アップデート機能が利用できません。boot 命令で個別のオブジェクトを作成するのはどうすればいいのでしょうか...?ちょっと、方法を思いつきません。

ModuleLoader.osax はここまで見てきた以外の機能も提供しています。プロジェクトごとの個別のライブラリの作成やモジュールを読み込んだときにフックをかけることもできます。また、モジュール間の依存関係を調べたり、検索パスにあるモジュールの検索を行うこともできます。それらは詳解しませんが、マニュアルと用語説明を参照すると理解できると思います。

最後に...。

AppleScript には他の言語のように共有されている、よく利用されている共通したライブラリというものがありません。例えば、PerlCPAN のように。Python なんかは電池内蔵といわれるぐらいライブラリが充実しています。

AppleScript には足りない命令、メソッド、関数が多くあります。AppleScript の歴史は長いです。が、これが標準といわれるほどのライブラリは存在しません。歴史が長く、足りないものが多いにも関わらず、それを補うライブラリがない。

多くの人が同じようなハンドラを毎回、毎回作っているのが現状です。どうしてなのでしょうか?

パソコンなんて人が楽をするためのただの道具です。AppleScript のようなプログラム言語はそのパソコンを使うことさえも省力化してしまおうとする言語です。なぜ、同じようなハンドラを何人も何人も毎回、毎回作っているのでしょうか?

ModuleLoader.osax は共有のライブラリを作るための一つの手段です。

確かに ModuleLoader.osax は、個性的な OSAX です。作者の AppleScript の使い方や考え方を反映しています。そのため使う人を選ぶと思います。

例えば、次のような記述。

property object_A : module "Value"
property loader : boot (module loader) for me

なんだかお呪いのようで、従来からのユーザーには取っ付きにくいものがあります。また、コンパイル時に property にスクリプトオブジェクトを代入するのを好まない人もいると思いますが、ModuleLoader.osax は property と組み合わせたときに真価を発揮します。

module loader 命令がスクリプトオブジェクトを返す...という考えようによっては強引な面もあります。設定ファイルも保存しますし。しかし、このような形で AppleScript をハックしないことには property にスクリプトオブジェクトを読み込めないのでしょう。

こういった仕様のためでしょうか。ModuleLoader.osax はマニア向けの OSAX だな、という感じがします。ただ、AppleScript に知悉している Script factory さんが出した答えがこの OSAX なのだとしたら、AppleScript でモジュールを気軽に扱う方法は他にないのだろうとも思います。実際、思いついたことのほとんどを既に Script factory さんが試しているのです。Google で検索して Script factory さんのサイトにたどり着くことのなんと多いことか...。

ModuleLoader.osax は AppleScript の初心者には取っ付きにくい OSAX だと思います。しかし、load script を置き換える load module を使うだけでも価値はあると思います。AppleScript に慣れてくれば、より高度な機能を使ってみるといいのです。

ModuleLoader.osax を利用するようになると AppleScript やスクリプトオブジェクト、ライブラリ/モジュールに対する認識が変わると思います。もしかすると、それこそが ModuleLoader.osax の真価なのでは...。

これからスクリプトライブラリを作ってみようと思っているなら、ModuleLoader.osax を試してみてはいかがでしょうか。なにか、いろいろと考えてしまうこと請け合いですから。

追記(10/03/28 19:42:11)- Script factory さんがこの記事についての補足(Script factory : ちゃらんぽらんさんの「ModuleLoader.osax の真価」へのコメント)を書いてくださっています。勘違いしていた部分もあるので、ぜひご一読を。

0 件のコメント :

コメントを投稿