ヘルパー
以下に示すのは、RexとSueという2つのオブジェクトによってヘルプされるオブジェクトのサンプルスクリプトです。
sayHello-- 感じよく始めましょう!
properties
helpers: (Rex, Sue),
birthdate: "May 14, 1942",
end properties
on sayHello
put "Greetings! My age is " & calculateAge()
end sayHello
このオブジェクトのsayHelloハンドラは、calculateAgeという関数を呼び出します。sayHelloハンドラが呼び出されると、calculateAge()が呼び出されることになります。このオブジェクトにはcalculateAge関数ハンドラがありませんが、いずれかのヘルパー、例えばRexがこの関数ハンドラを持っている場合、このオブジェクトに代わってそのハンドラを実行し、年齢計算を行います。
Meが指すのは?This Objectとは?
あるオブジェクトとそのヘルパーは緊密に連携して働くため、むしろ1つのオブジェクトのように機能します(三銃士の合言葉「みんなは一人のために、一人はみんなのために」を思い浮かべてください)。ヘルパー内のハンドラは、実行中、元のオブジェクトのハンドラであるかのように扱われます。この文脈では、meやmyが指しているのはヘルパーではなく、ヘルプされているオブジェクトなのです。
つまり、ヘルパーのスクリプト内で実行される文のメッセージは、ヘルプされているオブジェクトを(その上で間接的にだけヘルパーを)ターゲットにします。また、ヘルパーがmeやmyを使ってプロパティにアクセスした場合、アクセス先となるのはヘルプされているオブジェクトのプロパティになります。スクリプトでターゲットをそのスクリプト自身にしたり、スクリプト自身のプロパティにアクセスしたりする必要があるようなケースでは、meの代わりにthis objectを使うことができます。
メッセージパス内でのヘルパーの位置付け
オブジェクトのヘルパーは、メッセージパスの中でもオブジェクトと密接に結びついています。その処理用のハンドラを持たない(もしくは、処理後に次へ渡される)メッセージをオブジェクトが受け取ると、そのメッセージは次にオブジェクトのヘルパーに向かいます。そのメッセージを処理するヘルパーが1つもない場合にだけ、メッセージはメッセージパス内の他のオブジェクト、例えばbackScripts内のオブジェクトなどに沿って渡されます。
ヘルパーとして設計されたオブジェクト
どのオブジェクトも、他の1つまたは複数のオブジェクトのヘルパーになることができます。例えば、calculateAge()関数ハンドラを備えたRexというオブジェクトがあるとしましょう。そして、同calculateAge()機能を必要とするLunaという別のオブジェクトがあったとします。この場合、LunaのヘルパーリストにRexを加えることで、Lunaはその機能を得ることができます。しかし、Rexには他のハンドラも備わっている可能性があります。これらすべてがLunaの振る舞いの一部として含まれることは望ましくないかもしれません。Lunaが必要とする機能だけを与えるには、どうすればよいでしょうか?
この状況に対処する打ってつけの方法は、さまざまなオブジェクトに役立ちそうな振る舞い(ハンドラ)を取り出して、ヘルパーとしての機能に特化した新しいオブジェクトを作成するやり方です。例えば、calculateAge()ハンドラをRexから取り出してPersonオブジェクトに移し、その上でRexとLunaの両方のヘルパーリストにPersonを追加するということが可能です。1つのオブジェクトはヘルパーを何個でも持てるため、関連する振る舞いのグループをヘルパーオブジェクトに切り分けておくと、さまざまなオブジェクトにおいて多様な組み合わせでその機能を再利用できる非常に強力なツールとなり得ます。
ヘルパーとなるオブジェクトを設計する
多くのヘルパーオブジェクトは、数個のハンドラを記述したスクリプトのみの極めてシンプルなオブジェクトです。例えば、Personオブジェクトを、ここまで取り上げてきたcalculateAge()関数のような、1つのハンドラだけで始めても構いません。
to calculateAge-- 現在の年齢(歳)を返します
return (the date - my birthDate) / 365.25 days
end calculateAge
Me(My)について
ヘルパーについて理解しておくべき非常に重要な点として、ヘルパーのスクリプト内でmeやmyという語が使われるとき、それらが参照するのはヘルプされているオブジェクトとそのプロパティであって、ヘルパーとそのプロパティではないということがあります(ヘルパーを参照する必要がある場合はthis objectを使用します)。通常、これは、オブジェクトの一部として書かれたハンドラが、別のオブジェクトのヘルプ時にも、変更なしで正しく実行されることを意味します。
他のオブジェクトを基にオブジェクトを作成する
あるオブジェクトを別のオブジェクトのヘルパーとして使い、前者のオブジェクトの振る舞いを単に拝借する方法は非常に簡単で、ユーザーの側に特別な労力が求められることはあまりありません。主に他のオブジェクトのヘルパーとして機能させるオブジェクトに、スクリプトの一部の機能を切り分けようと思ったら、都合良く利用できる機能がさらにあります。
セクションの前の方で、new object式を使ってシンプルオブジェクトを作成する方法をご紹介しました。そこで、より一般的なnew object式の使用方法として、次の例のように基になるオブジェクトを与えることで、作成したい特定のオブジェクトタイプを指定する方法について触れました。
put new Person with (name:"Elizabeth", age:14) into daughter
この例のPersonは、新規作成オブジェクトを定義するのに使われるオブジェクト(「プロトタイプ」オブジェクト)の名前です。この例では、SenseTalk が「Person」という名前のオブジェクトを探し、パラメータとして与えられる初期プロパティと共にmakeNewObject関数メッセージをそのオブジェクトに送ります。これにより、新しいオブジェクトの仕様を正確にコントロールするチャンスがプロトタイプオブジェクトに与えられます。一般に、新しいオブジェクトは、プロトタイプオブジェクトをヘルパーとして備え、その全振る舞いを継承したり、プロトタイプの通常のプロパティ(スクリプトとヘルパー以外)のコピーを受け取ったりして、プロトタイプオブジェクト本体をベースにします。この正確な仕組みをさらに詳しく見ていきましょう。
プロトタイプオブジェクトの役割
プロトタイプオブジェクトにmakeNewObject関数が備わっていると、その関数によって新しいオブジェクトが作成され、返されます。プロトタイプオブジェクトがmakeNewObjectメッセージを処理しない場合、SenseTalkのビルトインmakeNewObject関数が呼び出され、当該プロトタイプオブジェクト(上記の例ではPerson)によってヘルプされた新規オブジェクトの作成、new式で与えられたオブジェクトのプロパティ(上記の例では「name」と「age」)の設定、まだ存在していないプロトタイププロパティの追加、そしてオブジェクトへの「initialize」メッセージの送信が行われます。したがって、ビルトインされた振る舞いの作用は、次のmakeNewObject関数と本質的には同じものになります。
to makeNewObject initialProperties
get new object with initialProperties helped by me
add properties of me to it
send "initialize" to it
return it
end makeNewObject
これと異なる振る舞いが必要であれば、単純にプロトタイプオブジェクト内でカスタムmakeNewObject関数を書くだけです。例えば、デフォルトの初期プロパティ値を割り当てたり、追加のヘルパーを含めたり、さらには別のプロトタイプオブジェクトを呼び出して新しいオブジェクトを作成したりしたい場合もあるかもしれません。
注目していただきたいのは、このデフォルトの振る舞いは、そのプロトタイプオブジェクトによってヘルプされる新しいオブジェクトを作成することであり、 したがって、どのオブジェクトもプロトタイプオブジェクトとして使用することができ、そこから作成される新しいオブジェクトのプライマリヘルパーになるということです。
新規オブジェクトのプロパティを初期化する
プロトタイプから新しいオブジェクトを作成すると、そのプロトタイプオブジェクトの全振る舞いがデフォルトで継承され、そのプロパティもコピーされます。また、新規オブジェクトはnew式で与えられたプロパティも受け取ります(これは、プロトタイプから与えられる対応のプロパティ値よりも優先されます)。さらに、プロトタイプオブジェクトによって新規オブジェクトに追加プロパティを与えることもできます。このやり方として、1つには先ほども述べたようにカスタムmakeNewObject関数を記述する方法がありますが、これが必要となるケースは稀です。よりシンプル(かつ好都合)な選択肢として、ビルトインmakeNewObject関数が新規オブジェクトに送る「initialize」メッセージを利用する方法があります(「initialize」メッセージはオブジェクトとそのヘルパーにだけ送られるもので、フルメッセージパスは通りません)。以下に、デフォルトのプロパティ値を与えるinitializeハンドラの例を示します。
to initialize
add properties (time:"12:00", priority:"Normal") to me
end initialize
monthプロパティとその値「May」を持つAppointmentプロトタイプオブジェクトに上記のハンドラがある場合、次のように新規アポイントメントを作成することが可能です。
put a new Appointment with (time:"8:30") into meeting
これで、Appointmentによってヘルプされる新しいオブジェクトが作成され、そのtimeプロパティは「8:30」に、そのpriority は「Normal」に設定されます。add propertiesコマンドは既に存在しているプロパティを置き換えないため、このinitializeハンドラで与えられた値よりもnew式で与えられた値の方が優先されます。新しいオブジェクトは、他にも、プロトタイプオブジェクトからコピーした、値が「May」のmonthプロパティも持つことになります。
空のオブジェクトを作成する
時には、プロトタイプから全プロパティのコピーを自動で受け取らない形の新規オブジェクトを作成したい場合もあるかもしれません。このためには、プロトタイプの前にemptyという語を使用します。
put a new empty Appointment with (time:"8:30") into meeting
この新しいオブジェクトはAppointmentによってヘルプされ、そのtimeプロパティは「8:30」に、そのpriority は「Normal」(initializeハンドラからの値)に設定されます。プロトタイプオブジェクトから直接コピーされるmonthプロパティ(またはその他のプロパティ)は受け取りません。
オブジェクトをテキストとして表示する
オブジェクトをテキストとして表示する場合、そのキーや値のデフォルト表示フォーマットはplistPrefix、plistSuffix、plistKeySeparator、plistEntrySeparatorプロパティによって決定されます(リストおよびプロパティリストの説明を参照)。ただし、次のようにスクリプトの一部としてasText関数のハンドラを実装することにより、オブジェクトが設定した方法で自身をテキスト文字列として表すことができます。
to handle asText
return "Part number " & my partNum
end asText
オブジェクトがasTextメッセージを処理しないものの、asTextプロパティを持っている場合、そのプロパティの値がオブジェクトの文字列表現として使われます。これは、スクリプトを全く持たないような一部のシンプルオブジェクトにおいて非常に便利です。
柔軟性をさらに高めるため、asTextハンドラもasTextプロパティも持たないものの、asTextFormatプロパティを持っているオブジェクトの場合、そのプロパティの値がmerge()関数によって評価されることで、オブジェクトのテキスト表現が与えられます。これは、オブジェクトの他のプロパティの値を結び付けることのできる、より動的なソリューションです。
set account to {type:"Savings", balance:1234.25,
asTextFormat:"[[my type]] Account: [[my balance]]"}
put account -- "Savings Account: 1234.25"
add 5050.50 to account's balance
put account -- "Savings Account: 6284.75"
オブジェクトの中身をチェックする
スクリプトでcontainsまたはis in演算子を使って特定の値がオブジェクトに含まれているかどうかをチェックする場合、オブジェクト(またはそのいずれかのヘルパー)に、希望の方法でこのテストを実行する関数を実装することができます。このいずれかの演算子を使用してオブジェクトまたはプロパティリスト内における値の有無をチェックすると、containsItem関数メッセージがそのオブジェクトに送られます。オブジェクトがこのメッセージ用のハンドラを実装していれば、そのハンドラに2つのパラメータ(検索対象の値と、テストで大文字小文字を区別すべきか否かのブーリアン)が渡され、ハンドラがオブジェクト内における指定値の有無を示すtrueまたはfalseを返します。
オブジェクトがcontainsItemメッセージを処理しない場合、SenseTalkの実装による当該関数が呼び出されます。このビルトイン関数の振る舞いは、the objectContainsItemDefinitionグローバルプロパティの設定によって構成可能です。このプロパティをAsTextContains(デフォルト)に設定すると、オブジェクトのテキスト値(上記参照)に検索値が含まれている場合に、containsItem関数がtrueを返します。NestedValueContainsに設定すると、オブジェクトの各値に演算子が適用され、いずれかの値に検索値が含まれている場合に式がtrueを生成します。最後に、KeyOrValueEqualsの設定だと、オブジェクトのいずれかのキーまたは値が検索値と等しい場合にtrueが返されます。
上級向けトピック: Early Helper
オブジェクトのヘルパーは、オブジェクト自身によって処理されていないオブジェクト宛メッセージを潜在的に処理することによって、追加的な振る舞いを与えるものです。オブジェクトは、オブジェクト独自のハンドラを設けることにより、ヘルパーが提供するハンドラを上書きすることができます。しかし、時にはその逆の機能、つまりオブジェクト自身が定義した振る舞いを上書きできるヘルパーをオブジェクトに備える機能が役に立つ場合もあります。そのためにあるのがEarly Helperです。オブジェクトが受け取ったメッセージは、まずオブジェクトのearly helperに送られ、それからオブジェクト自身へ、そして最終的にそのヘルパーへと送られます。
オブジェクトにヘルパーを割り当てる
既に見てきたように、オブジェクトの新規作成時にヘルパーを割り当てる方法としては、スクリプトファイルのプロパティ宣言内にヘルパーのリストを含めたり、プロパティリストやnew object式の後の「helped by」節にヘルパーをリストアップしたり、最初のヘルパーとなるプロトタイプオブジェクトからオブジェクトを作成したりといった、いくつかの方法があります。
さらに、オブジェクトのヘルパーリスト(およびearly helperリスト)は、スクリプトでいつでも動的に変更することが可能です。やり方は、helpersまたはearly helpersプロパティにアクセスし、必要に応じてオブジェクトの挿入または削除を行うだけです。
insert CleverTricks into the helpers of Fritz