ハンドラ
ハンドラはメッセージリスナーです。メッセージを受信してそのメッセージに基づくアクションを実行するために、オブジェクトは特定のメッセージ用のハンドラを持っている必要があります。
メッセージを処理する
オブジェクトの振る舞いは、送られてくるであろう各種メッセージに対する処理の仕方によって定義づけられます。オブジェクトは、メッセージを受け取ると応答し、スクリプト化されたアクションを実行します。ただしこれは、その個別のメッセージに対応したハンドラをオブジェクトが備えている場合だけです。すべてのスクリプトは、そのオブジェクトが処理に関与する各種メッセージに対応した1つ以上のハンドラ(スクリプトと同名でメッセージを処理する、スクリプトのinitialハンドラを含む)から成り立っています。オブジェクトは、自身が持つハンドラで対応していないメッセージ受け取ると、そのメッセージを無視します。
以下に示すのは、greetTheUserメッセージ用の非常に簡単なハンドラです。
to greetTheUser
put "Welcome to SenseTalk. Happy scripting!"
end greetTheUser
コマンド、関数、汎用ハンドラ
メッセージハンドラには、主にコマンド、関数および汎用ハンドラの3種類があります。コマンドハンドラは、onで始まり、コマンドによって送られてくるメッセージを処理します。コマンドハンドラは、一般に一連のアクションを実行します。関数ハンドラは、functionで始まり、関数呼び出しメッセージを処理して値を返します。汎用ハンドラは、to handle(または単にto)で始まり、コマンドおよび関数メッセージの両方を処理することができます。注意してほしいのは、コマンドハンドラが値を返すこともあれば、関数ハンドラが値を返す他にアクションを実行することもある点です。各種ハンドラ間の実際の違いは、それぞれが処理するメッセージの種類だけです。関数メッセージの結果としてコマンドハンドラが実行されることはありませんし、コマンドメッセージによって関数ハンドラが呼び出されることもありません。
Initialハンドラ
スクリプトは、to、on、functionというキーワードで始まる、明示的に宣言されたハンドラの他に、initialハンドラを持つことができます。initialハンドラは、(スクリプト冒頭のプロパティ宣言をスキップした)スクリプトの開始部から最初の明示ハンドラまたはプロパティ宣言までのすべての文で構成されます。initialハンドラは、汎用的なto handleタイプのハンドラとして扱われ、スクリプトと同じ名前のメッセージをどれでも処理します。名前のないオブジェクト(スクリプトファイルから読み込まれるのでないもの)の場合は、runコマンドまたは関数を使ってinitialハンドラを呼び出すことができます。
渡されたパラメータを受け取る
呼び出し元のスクリプトから渡されるパラメータを受け取ることになっているハンドラでは、メッセージ名の後にそうしたパラメータの名前をリストアップすることができます(または、ハンドラ内の最初の行のparams宣言内でこれを行うこともできます。これは、initialハンドラにおいて特に便利です)。例えば、ここに示す関数では、まず除数がゼロでないかチェックしてから、2つの数字の商を求めます。
function quotient dividend, divisor
if divisor is zero then return zero
else return dividend/ divisor
end quotient
この例におけるdividendとdivisorという語は、quotientハンドラ内でローカル変数のように扱われ、その初期値は、呼び出し元の関数によって渡される最初の2つのパラメータから既に割り当てられています。SenseTalkでは、コマンドや関数を呼び出す際に、ハンドラが予期しているであろう数に関係なく、パラメータを何個でも使用することができます。ハンドラで命名したパラメータの数よりも、呼び出し元のスクリプトによって渡される値の数の方が少ない場合、初期値のない命名パラメータについては空に設定されます。
ハンドラが宣言している命名パラメータの数よりも、ハンドラに渡される値の数の方が多い場合は、param()またはparameterList()関数を使って、ハンドラ内から追加の値にアクセスすることができます。渡されたパラメータの数を取得するには、paramCount()関数を使います。以下に示す例では、こうした関数を使って、渡されたすべてのパラメータ値の中から、数字でない値を対象から外した上で、中央(中間)値を探し出しています。
to handle medianNumber
set numList to be an empty list
repeat with n=1 to the paramCount-- 全パラメータを1つ1つ処理していきます
if param(n) is a number then insert param(n) into numList
end repeat
sort the items of numList in numeric order
return the middle item of numList
end mediaNumber
ハンドラに渡されるパラメータの変数名を処理する方法は他にもあります。それは、ハンドラ名の後に(またはparams宣言内で)1つ以上の変数名をリストアップして、最後の変数名の後に点を3つ(...)続けるやり方です。これにより、その変数は追加的なすべてのパラメータを含むリストに設定されます。
to quoteAndJoin joiner, stuffToJoin...
get stuffToJoin joined by (quote & joiner & quote)
return quote & it & quote
end quoteAndJoin
コンテナとして(参照によって)渡されるパラメータ
時には、呼び出し元のスクリプト内の1つまたは複数の値をハンドラが変更できると便利な場合があります。SenseTalkでは、呼び出し元のスクリプトが許可した場合だけ、これを行うことができます。その場合、1つまたは複数のパラメータを「参照によって」または「コンテナ」として渡します(詳しくはコンテナの参照の説明を参照)。説明のため、2つの値を交換する非常に簡単なコマンドハンドラを例に挙げます。
on swapValues a,b
put a into temp
put b into a
put temp into b
end swapValues
通常の方法で呼び出された場合、このコマンドは呼び出し元のスクリプトに影響を及ぼしません。
swapValues x,y -- このコマンドだと、xとyは変わらないままです
しかし、呼び出し元のスクリプトによってパラメータが明示的にコンテナとして渡された場合には、その値を変更することができます。
swapValues container x, container y -- xとyの値が交換されます
結果を返す
先に述べたように、関数ハンドラは呼び出し元のスクリプトに値を返し、それが関数呼び出し式の値として使われます。関数ハンドラが明示的に値を返さない場合、戻り値は単に空になります。しかし、通常、関数を設けるのは値を提供するためなので、ほとんどの場合、関数ハンドラはreturn文で終わります。return文には、戻り値である1つのパラメータ(式も可)を含まなければなりません。必要であれば、次のように、複数の値をリストとして返すこともできます。
return (min(source), max(source), average(source))
メッセージを渡す
ハンドラは、passコマンドを使って、受け取ったメッセージを処理しないことを選択できるほか、何らかのアクションを実行してからメッセージパッシングパス内の後続の別スクリプトにメッセージを渡すこともできます。
pass message
passコマンドのバリエーションについては、メッセージの操作で説明しています。
Any(<any>)メッセージを処理する
各ハンドラが処理するのは、1つの名前だけを持ったメッセージです。したがって、to driveハンドラは、driveメッセージが送られてきたときだけ呼び出されます。稀に、どのような名前のメッセージでも受け取れるハンドラを作成することが役に立つ場合があります。これは、メッセージ名の代わりに<any>を指定することで実現できます。この機能が特に役立つのは、 getPropハンドラ内や、frontScripts内のスクリプトにおいてです。
<any>ハンドラに関しては、特別なルールがいくつかあります。スクリプトに特定のメッセージ用のハンドラがある場合、<any>ではなくそちらのハンドラが呼び出されます。<any>ハンドラが呼び出されるのは、そのスクリプトで他に処理を行うハンドラがないメッセージの場合だけです。やって来るメッセージをどれでも処理する<any>ハンドラ特有の能力のため、自身を呼び出すことはSenseTalkによって特別にブロックされています。そうでないと、on <any>ハンドラ内のeachコマンドによって自分自身を呼び出すことになり、すぐに再帰の悪夢に陥ってしまいます。
意図しないブロッキングメッセージを回避するため、<any>ハンドラの使用には注意して臨むべきです。param(0)関数を使うと、送られてきた実際のメッセージ名を取得することができます。これを基に、適当なアクションを選択したり、ハンドラで扱わないメッセージを渡したりします。
on <any>
if param(0) does not begin with "x_" then pass message
-- x_で始まるコマンドを処理するコードをここに置きます
end on <any>
未達メッセージを処理する(上級)
SenseTalkは、送られたメッセージのハンドラが見つからないとき、直ちにエラーを起こすのではなく、オリジナルの未処理メッセージのターゲットにundeliveredMessageメッセージを送ります。オブジェクトは、処理を行えそうな別のオブジェクトにオリジナルメッセージを渡そうとしたり(pass original message to ...コマンドを参照)、他の何らかの方法で問題への対処を試みるためのundeliveredMessageハンドラを実装することができます。undeliveredMessageハンドラがpass undeliveredMessageコマンドを実行する場合、通常のエラーが起こります。
pass original message to objectコマンドを使うと、未達のメッセージを別のオブジェクトに渡そうとすることができます。その別のオブジェクトがメッセージを処理すれば、それが終わりとなり、現在のハンドラの実行が終了します。別のオブジェクトがオリジナルメッセージを処理しなければ、実行は継続します。このようにして、undeliveredMessageハンドラは、処理を行えそうな1つまたは複数の別オブジェクトにオリジナルメッセージの送達を試みることができるのです。
to handle undeliveredMessage
repeat with friend = each item in my friends
pass original message to friend
end repeat
pass undeliveredMessage -- 諦めて失敗とします
end undeliveredMessage