表題の通り。ほかの言語なんかでは文字列でメソッドを指定して実行するような機能(というか...機能ではないですね)があったりします。クラスを文字列で指定して取得とか。こういうことが AppleScript でできるのかな?できないのかな?どうなのかな、と常々考えていました。
普通に考えるとできません。できないけどしたい。そりゃ、無茶だ。でも、したい。
方法はない事もないのです。Script factry さんが 2007 年 3 月 22 日に「ハンドラを文字列で指定して実行する」としてその方法について書かれています。
この記述が公開されたときに既にハンドラを文字列で指定して実行する方法は見つけていたのですが、なにせその動作原理を説明するのが難しくて公開を控えていたのでした。いまでも説明するのが難しい。人には説明できないこのもどかしさ。
ともかく、以下のようにすればハンドラを文字列で指定して実行できます。
set theHandler to handlerWithName("getnum")
theHandler()
--> 81
set theHandler to handlerWithName("greeting")
theHandler("Mac")
on getNum()
return 81
end getNum
on greeting(yourName)
display dialog "Hello, " & yourName
end greeting
on handlerWithName(str)
set dir to path to temporary items folder from user domain as Unicode text
set fileName to "tmp.scpt"
set scptFile to dir & fileName
set posixFile to POSIX path of scptFile
set str to quoted form of str
do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
return run (load script file scptFile)
end handlerWithName
わはは。力技だ。実際にはハンドラを文字列で指定して実行しているのではなく、ハンドラを取得しているのですが(ハンドラは変数に入れる事ができる、という事を利用しているのです)。
これでスクリプト実行時に動的にハンドラやスクリプトオブジェクトを変えることができます。もちろん、多少の速度低下は否めませんが。というか、こういう方法よりもっと簡単な『文字列で指定したハンドラやスクリプトオブジェクトを実行、あるいは取得する』方法はないでしょうか?
何か方法があれば、blackcharan@gmail.com までご一報を。
と、このスクリプトが完成したのが 2006 年の晩秋。そして、最初に書いたように Script factry さんがスマートな方法を公開されたのでした。この方法を見た時、はたと膝を叩いてしまいましたよ。
さて、唐突ですがここで問題です。
以下の run script 命令でなぜエラーが出るのでしょうか?
set x to 10
run script "x"
--> x変数は定義されていません。
そもそもこのエラーによって文字列でハンドラを実行する方法を思いついたのでした。素敵な回答をいただいた方には、感謝の気持ちを込めて個人的にほっぺにチュッてします。おでこでも可。
このエラーが意味する事はさらにあります。Script factry さんが公開された方法でならできる事があります。しかし、先に記述したスクリプトではこれができません。それはいったいなんでしょうか?
もう一つ。かりやんさんが書かれていた事なのですが、スクリプトオブジェクトへハンドラがインストールできるという記述。しかし、これはちょっと違うような気がします。スクリプトオブジェクトにハンドラをインストールできる(ように見える)のは、そのスクリプトオブジェクトとハンドラが同一スクリプトファイル内で定義されているときだけです。
script Obje
end script
on x(num)
return num
end x
set x of Obje to x
tell Obje to x(10)
一見、インストールできているように見えます。が、本当にインストールできるなら load script 命令で読み込んだスクリプトオブジェクトでも同じようにできるはずです。が、それはできません。上記のスクリプトがなぜ動くのか。以下のスクリプトを試してみれば、その原因が見えてきます。
script Obje
end script
on x(num)
return num
end x
--set x of Obje to x
tell Obje to x(10)
スクリプトオブジェクトにハンドラをインストールしている部分をコメントアウトしました。それでもこのスクリプトは動きます。つまり、同一スクリプト内でのハンドラの有効範囲(可視性、スコープ)です。以下のようにしても動く事からハンドラは同一スクリプト内にある限り、ほとんどの場所から参照できるのです。
script Obje
x(10)
end script
on x(num)
return num
end x
tell Obje to run
load script 命令で読み込んだスクリプトオブジェクトは、この有効範囲が異なります。読み込んだ方と読み込まれた方はそれぞれ別の有効範囲を持っています。つまり、読み込んだスクリプトオブジェクトにはハンドラのインストール(?)ができないのです。
問いとして出題した run script 命令が動かないのは、この有効範囲の問題です(run script 命令で文字列をスクリプトとして実行すると新しいスクリプトオブジェクトが作成される、と考えると分かりやすい。だから、変数が未定義になるのです)。
しかし、例外があります。読み込んだスクリプトオブジェクトにハンドラがインストールできないのは、読み込んだスクリプトオブジェクトにハンドラ(変数)が定義されていないからです。逆に言えば、定義さえされていればいいのです。定義されているなら読み込んだ方、読み込まれた方でハンドラが共有されます(されてしまいます)。この事を利用したのが最初に紹介したスクリプトです(蛇足ですが実際はハンドラだけでなく変数でもスクリプトオブジェクトでも文字列で指定する事ができます)。
on handlerWithName(str)
set dir to path to temporary items folder from user domain as Unicode text
set fileName to "tmp.scpt"
set scptFile to dir & fileName
set posixFile to POSIX path of scptFile
set str to quoted form of str
do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
return run (load script file scptFile)
end handlerWithName
そして、Script factry さんが公開された方法でならできる事というのは、文字列でハンドラを呼び出すハンドラを他のハンドラ内から利用する、という事です。先に紹介したスクリプトでは以下の事ができません。
x()
on x()
set theResult to handlerWithName("greeting")
theResult("Mac")
--> «script» は theResult メッセージを認識できません。
end x
on getNum()
return 81
end getNum
on greeting(yourName)
display dialog "Hello, " & yourName
end greeting
on handlerWithName(str)
set dir to path to temporary items folder from user domain as Unicode text
set fileName to "tmp.scpt"
set scptFile to dir & fileName
set posixFile to POSIX path of scptFile
set str to quoted form of str
do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
set theReult to run (load script file scptFile)
end handlerWithName
Script factry さんが公開された方法なら、このような別のハンドラ内からの利用でも問題ありません。
Script factry さんの方法を見習って解決するなら、以下のようになります。
script caller
property method : missing value
on request(argument)
if argument is "" then
my method()
else
my method(argument)
end if
end request
on handlerWithName(str)
set dir to path to temporary items folder from user domain as Unicode text
set fileName to "tmp.scpt"
set scptFile to dir & fileName
set posixFile to POSIX path of scptFile
set str to quoted form of str
do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
set method of me to run (load script file scptFile)
end handlerWithName
end script
x()
on x()
tell caller
handlerWithName("greeting")
request("Mac")
handlerWithName("getnum")
request("")
--> 81
end tell
end x
on getNum()
return 81
end getNum
on greeting(yourName)
display dialog "Hello, " & yourName
end greeting
いずれにしてもScript factry さんの方がスマートですが...。ちなみになぜ、これが動くかはナゾです。さぁ、Let's thinking!!
0 件のコメント :
コメントを投稿