2012-06-27T18:32:51+09:00

AppleScript の global 変数の使いどころ

皆さん、AppleScript のglobal 変数を活用していますか?property とどう使い分けるのかよくわからんかったりしませんか?

結論から言ってしまうと、

要するに、global 変数はそうそう使い道がないということだ。いきなりこんなことをまくしたてられてもよくわからないでしょうから、順を追って、他のサイトでは見ることができない、当サイトオリジナルの global 変数の利用方法と知見をご披露します。

property vs global 変数

global 変数とは、ご存知の通り宣言されているところで値を共有される変数です。ハンドラ間で値を共有する変数が欲しいとします。次のようにハンドラの上位で global 変数を宣言すれば、複数のハンドラで共有できる変数になる。

global _value

on set_global()
set _value to "hello"
end set_global

on access_global()
return _value
end access_global

set_global()
access_global()
--> "hello"

ちなみに、property を使っても同じことができる。

property _value : missing value

on set_global()
set _value to "hello"
end set_global

on access_global()
return _value
end access_global

set_global()
access_global()
--> "hello"

property の場合は、初期値の指定が必要になるけど、それ以外は一緒です。この場合、property と global 変数のどちらを使うべきかというと、property をお勧めする。トップレベルで global 変数を定義する分には property と差はないのだけど、ハンドラの中やスクリプトオブジェクトの中で global 変数を定義した場合、その global 変数はトップレベルの property や run ハンドラ内の変数と値を共有するようになる。global 変数のスコープは property よりずっと広くて、直感的でないところがある。使い分けに悩んだら、property にしておけということだ。

特定のハンドラ間でのみ変数の値を共有したくて、ハンドラの中で global 変数を宣言するという選択もある。

on set_global()
global _value
set _value to "hello"
end set_global

on access_global()
global _value
return _value
end access_global

set_global()
access_global()
--> "hello"

でも、これよりもっといい方法がある。変数を共有するハンドラを限定したければ、それらのハンドラはスクリプトオブジェクトの中に押し込めるべきだと思う。

script ScriptA
property _value : missing value

on set_global()
set _value to "hello"
end set_global

on access_global()
return _value
end access_global
end script

tell ScriptA
set_global()
access_global() --> "hello"
end tell

このように、スクリプトオブジェクトでくくった方が共有する変数(上のサンプルの場合では property)のスコープが明示できるし、ハンドラを分類することになるのでコードが分かりやすくなるはず。そしてオブジェクト指向プログラミングへの道が開けてくる。

global 変数を経由して Module Script 間でのハンドラの呼び出し

さて、今のところ global 変数はいらない子のように思える。でも、global 変数のトップレベルの property にまでスコープが及ぶという性質には、重要な応用が一つある。一つだけしか無いんだけど、知ってなければあかんと思うくらい重要だ。それは、他のスクリプトファイルを load script コマンドによってロードして、ライブラリとして利用する場合だ。

あるスクリプトを load script コマンドによってトップレベルのスクリプトにロードすることを考える。ロードされるスクリプト(Module Script と呼ぶことにする)の中で、トップレベルの property と同名の global 変数を宣言しておくと、トップレベルのプロパティにアクセスできるようになる。

でも、これだけだとご利益が分からないね。単に「Module Script からトップレベルと値を共有できます」と言っても、トップレベルの方から Module Script のハンドラを呼び出して値を渡してやればいいんじゃね?という考えもある。まったくもって、その通り。本番はこれからだ。

次に、二つの ModuleScript を load script することを考える。そして、それらをトップレベルの property に設定する。すると、global 変数を経由してModule Script 間でのハンドラの呼び出しが行えるようになる。

まず、ロードされるモジュールを ModuleA, ModuleB とする。

global ModuleB

on call_module_b()
ModuleB's who()
end call_module_b
on who()
return "I am ModuleB"
end who

この二つのスクリプトをトップレベルで load script コマンドで読み込んで、property に設定する。

property module_path : (path to scripts folder as text) & "Modules:samples for global variable:"
on load_module(module_name)
return load script alias (module_path & module_name)
end load_module

property ModuleB : load_module("ModuleB.scpt")

tell load_module("ModuleA.scpt")
call_module_b() --> "I am ModuleB"
end tell

このスクリプトでは、ModuleA, ModuleB を ModuleA.scpt, ModuleB.scpt として、"ホームフォルダ/Library/Scripts/Modules:samples for global variable" という場所に保存して、それらを読み込んでいる。ModuleA.scpt, ModuleB.scpt の置き場所は、property module_path で定義している。このように、ロードした ModuleA から別のファイルからロードした ModuleB のハンドラを実行できちゃう訳だ。いかしてるだろう〜。

絵に描いてみるとこんな感じかしら?

このテクニックを覚えるとスクリプトのコードを複数のファイルに自在に分割できるようになる。global 変数を経由して他のModule Scriptのハンドラを呼び出すテクニックを使わないと、Module Script は自分の中で閉じたコードでなければならないことになる。そうすると、コードの分割の単位がすごく限られてくる。複数の Module Script で同じようなコードを抱え込むはめになって、何のためにコードを分割しているのか分からなくなる。

規模の大きなコードになると、役割ごとに複数のファイルに分割しないとコードの見通しが悪くなるから、load script と global 変数を活用はとても役に立つ。また、よく使うコードはライブラリとして色んなスクリプトでロードして使い回すことを覚えれば、さらにハッピーになれる。でも、この方法は運用してみれば分かるけど、結構問題がある。それは、トップレベルですべての Module Script のロードを管理しなければならないことだ。

load script と global 変数を使ったライブラリ構築の問題点

AppleScript に習熟して、いくつもの Module Script をライブラリとして扱うようになったとする。そして、Module Script どうしで依存関係が複雑になってきたとする。例えば、先ほどの例のように ModuleA が ModuleB を必要としています。さらに、ModuleB が別の ModuleC を必要としているとします。こんな関係が色んなモジュールで発生していると、把握しきれなくちゃっちゃう。

トップレベルのスクリプトからは、ModuleA のハンドラしか実行しないんだけど、依存関係があるものだからトップレベルで ModuleB, ModuleC をロードしてやらなければいけない。サンプルを示すと、次のようになる。ModuleA はさっきと同じで、ModuleC と ModuleC に依存した ModuleB を示すぜ。

global ModuleC

on who()
return ModuleC's who()
end who
on who()
return "I am ModuleC"
end who

これらをロードするトップレベルスクリプトは次のようになる。

property module_path : (path to scripts folder as text) & "Modules:samples for global variable:"
on load_module(module_name)
return load script alias (module_path & module_name)
end load_module

property ModuleB : load_module("ModuleB-with-C.scpt")
property ModuleC : load_module("ModuleC.scpt")

tell load_module("ModuleA.scpt")
call_module_b() --> "I am ModuleC"
end tell

トップレベルからは ModuleA しかアクセスしないのに、ModuleB とか ModuleC 読み込むのはめんどくさいと思わないかい?モジュールが多くなってくると、モジュール間の依存関係なんて把握しきれなくなるぜ。よけいなモジュールを読み込んだり、必要なモジュールのロードを忘れてエラーが起きたりする。コードを分割してよけい大変になった、なんてことになりうる。素の AppleScript では、本格的なライブラリの構築は現実的じゃないんだ。

ModuleLoader の活用

こんな問題は ModuleLoader を使えば、全部解決できる。ModuleLoader の細かい使い方は、マニュアルを見ていただくとして、ModuleLoader を使った場合のサンプルを示そう。

property ModuleB : module

on call_module_b()
ModuleB's who()
end call_module_b
property ModuleC : module

on who()
return ModuleC's who()
end who

以上が、ModuleLoader によるロードに対応した ModuleA.scpt, ModulB.scpt です。依存するモジュールは property で示すことになる。property の初期値として module コマンドを実行してモジュールの置き場所である印を付けておく。すると、このスクリプトが ModuleLoader でロードされた時、その property 名と同じスクリプトを勝手に探してきてロードしてくれる。これが、ModuleLoader を使えば global 変数を使う必要なくなるという意味だ。

次に、トップレベルスクリプトを示そう。

property ModuleA : module

boot (module loader) for me
ModuleA's call_module_b()
--> "I am ModuleC"

トップレベルでもロードしたいモジュールは property と module コマンドで指定する。そして boot (module loader) for me を実行すると、モジュールのロードが行われる。この実行文は少々呪文のようだが、それ以外は圧倒的にスリムになったでしょう。一行くらい目をつむってくれ。トップレベルで ModuelB, ModuleC を読み込んでやる必要はないんだぜ。すべてが自動的に良きに計らわれる。

AppleScript を書きまくって Mac を便利にしたいと思ったら、 ModuleLoader は Must Have だと思うのだけど、ご賛同いただけませんか?

実行時の property の追加

さて、話を global 変数に戻そう。先にも言ったように、global 変数はどこで宣言されていようとも、そのスコープにはトップレベルの property が含まれる。global 変数と同名の property がトップレベルに無かったらどうなるか。なんと、トップレベルに勝手に property が付け加わっちゃうんだぜ。証拠を示そう。

on set_global()
global _value
set _value to "hello"
end set_global

on access_property()
my _value
end access_property

set_global()
access_property() --> "hello"

このスクリプトでは、set_global ハンドラの中で global 変数の宣言と設定をしている。 access_property は global 変数 _value のスコープの外なんだけど、my を付けて property へアクセスしてやれば、global 変数に設定した値が取得できちゃうぜ。ちなみに、access_property ハンドラの中の my をはずしてやると _value はローカル変数になるのでエラーが発生するようになる。

つまり、global 変数を使えば、実行時にトップレベルに property を追加できちゃうんだ。おもしろいだろう〜。まあ、何の役に立つか分からんけどな。

ところで、global 変数は property と違って実行のたびに値が保存されない、という解説を見たことがあるけどそれは間違いだ。global 変数は勝手にトップレベルの property をでっち上げさえするのだから、その性質は property と一緒。global 変数に設定された値は、実行後もスクリプトの中に残ります。global 変数はトップレベルの property への参照と理解するのが正しいと思う。