文字列で指定したハンドラを実行する

表題の通り。ほかの言語なんかでは文字列でメソッドを指定して実行するような機能(というか...機能ではないですね)があったりします。クラスを文字列で指定して取得とか。こういうことが AppleScript でできるのかな?できないのかな?どうなのかな、と常々考えていました。

普通に考えるとできません。できないけどしたい。そりゃ、無茶だ。でも、したい。

方法はない事もないのです。Script factry さんが 2007 年 3 月 22 日に「ハンドラを文字列で指定して実行する」としてその方法について書かれています。

この記述が公開されたときに既にハンドラを文字列で指定して実行する方法は見つけていたのですが、なにせその動作原理を説明するのが難しくて公開を控えていたのでした。いまでも説明するのが難しい。人には説明できないこのもどかしさ。

ともかく、以下のようにすればハンドラを文字列で指定して実行できます。

Script Editor で開く

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 命令でなぜエラーが出るのでしょうか?

Script Editor で開く

set x to 10

run script "x"
--> x変数は定義されていません。

そもそもこのエラーによって文字列でハンドラを実行する方法を思いついたのでした。素敵な回答をいただいた方には、感謝の気持ちを込めて個人的にほっぺにチュッてします。おでこでも可。

このエラーが意味する事はさらにあります。Script factry さんが公開された方法でならできる事があります。しかし、先に記述したスクリプトではこれができません。それはいったいなんでしょうか?

もう一つ。かりやんさんが書かれていた事なのですが、スクリプトオブジェクトへハンドラがインストールできるという記述。しかし、これはちょっと違うような気がします。スクリプトオブジェクトにハンドラをインストールできる(ように見える)のは、そのスクリプトオブジェクトとハンドラが同一スクリプトファイル内で定義されているときだけです。

Script Editor で開く

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 Editor で開く

script Obje
end script

on x(num)
    return num
end x

--set x of Obje to x

tell Obje to x(10)

スクリプトオブジェクトにハンドラをインストールしている部分をコメントアウトしました。それでもこのスクリプトは動きます。つまり、同一スクリプト内でのハンドラの有効範囲(可視性、スコープ)です。以下のようにしても動く事からハンドラは同一スクリプト内にある限り、ほとんどの場所から参照できるのです。

Script Editor で開く

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 さんが公開された方法でならできる事というのは、文字列でハンドラを呼び出すハンドラを他のハンドラ内から利用する、という事です。先に紹介したスクリプトでは以下の事ができません。

Script Editor で開く

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 Editor で開く

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 件のコメント :

コメントを投稿