命令作ってラクしよう

利用者定義命令って何だ?

ハンドラで受け止める」で紹介したように、AppleScriptではスクリプト中の命令がメッセージとなりハンドラへ送られます。
 そしてメッセージを受け取ったハンドラが、アプリケーションの起動やファイルの開閉などの処理を行います。
 前回はメッセージとなる命令に、Mac OSが用意しているrunopenidle——などを使いましたが、自分で作成した命令を使うこともできます。
 これを利用者定義命令(user handler)と呼びます。

 利用者定義命令の名前は、変数の場合と同じでアルファベットと数字を組み合わせて作ります。
 定義方法は次のとおりです。

on 利用者定義命令の名前()
	処理する内容
end 利用者定義命令の名前

 runハンドラに似ていますが、onの後の名前にカッコをつける点が違います(注1)。

 利用者定義命令のハンドラはスクリプト中のどこに書いても構いません。
 また、同じ名前でなければハンドラはいくつでも作成できます。

 利用者定義命令を使う主なメリットには、次の4つがあります。

  1. 複数の命令を1つにまとめられるので、スクリプトが短く。
  2. 全体でなく、ハンドラ単位で把握すればいいので、スクリプトの理解が容易。
  3. ハンドラを変更すると、利用者定義命令を使っているすべての部分に反映されるので管理が簡単。
  4. ハンドラ単位でコピーすることで、ほかのスクリプトへの流用が簡単。

利用者定義命令を使って積極的に楽をする

 とまぁ、いいことだらけなので、利用者定義命令は、どんどん使いましょう。と言われても、まだピンと来ないでしょうから具体的なスクリプトを使って説明します。

 次のスクリプトは、0〜3回の間で警告音を再生します。

randomBeep() -- 利用者定義命令の呼び出し
randomBeep()
display dialog "まだまだ"
randomBeep()
randomBeep()
display dialog "終わり"

on randomBeep()
	display dialog "実行中"
	beep(random number 3)
end randomBeep

 このように、利用者定義命令は同じスクリプト中で何度でも使えます。
 頻繁に行う処理をハンドラにしておけば、ずいぶん楽できることがわかるでしょう。
 ハンドラを使わずに同じスクリプトを書くと、次のようになります。

display dialog "実行中"
beep(random number 3)
display dialog "実行中"
beep(random number 3)
display dialog "まだまだ"
display dialog "実行中"
beep(random number 3)
display dialog "実行中"
beep(random number 3)
display dialog "実行中"
beep(random number 3)
display dialog "終わり"

 display dialog "実行中"beep(random number 3)を何度も書かなければなりません。
 ここでは2行なので、それほどではありませんが、同じ処理をしている部分がさらに多くの行数を使っているとしたら、ハンドラを使うことのメリットは、ますます強くなります。
 だいたい、全く同じ数行のスクリプトを複数箇所で書いていたらハンドラにまとめる、というのが目安になるでしょう(注1)。

 こんどは、乱数にもう少し変化を持たせたいので、次のように書き換えた場合を考えてみましょう。

beep(some item of {1, 3, 4, 10})

 ハンドラを使っている場合は、「beep(random number 3)」の1カ所だけを書き換えれば済みます。
 ハンドラを使わない場合は、5カ所も書き換えなければならず、かなり面倒です。
 これでは、書き換えればスクリプトが良くなる事が分かっていても、面倒だと言う理由で書き換えなくなってしまいます。

 スクリプトを作成していると、乱数などの数値の微調整を行いたくなるものです。
 利用者定義命令を使って書き換えを容易にしておけば、最終的にはスクリプトの質(読みやすさや、実行性能)の向上につながることでしょう。

 複数の箇所から呼ばれていなくても、画面内にハンドラ全体を表示しきれなくなったら長すぎる、というくらいを目安に、こまめにスクリプトをハンドラ単位に分割することをお勧めします(注2)。

命令に値を渡す

 これまで紹介してきた命令は、引数を渡すことによって同じ命令でさまざまな処理を行えました。
 同様に、利用者定義命令でも引数を利用でき、その方法はいくつかあります(注1)。

 まずは、一番基本的な位置渡しの利用者定義命令を見てみましょう。  引数の使い方は、次のようになります。

命令の名前(値,...)

on 命令の名前(変数名,...)
	処理する内容
end 命令の名前

 openハンドラと似ていますが、引数をカッコで囲む点が異なります。

 次のスクリプトは、ボタンを1つだけ配置するdisplay dialog命令で、引数を使ってアイコンを変更しています。

okDialog("1つのボタンだけ", 1)
okDialog("表示は変えられます", 0)
okDialog("何度も使うときは", 2)
okDialog("利用者定義命令", 1)

on okDialog(text2display, iconNumber)
	display dialog text2display buttons {"OK"} default button "OK" with icon iconNumber
end okDialog

 引数はカンマで区切っていくつでも使えますが、順番は固定です。順番を間違えないように気をつけましょう。

ラベルで値を渡す(1)

 引数が1つの場合は問題はありませんが、引数を順番に並べていくと、それぞれの引数の意味が把握しづらくなります。
 そこで、順番ではなくラベルで引数を区別する、ラベル渡しの利用者定義命令を紹介しておきましょう。

命令の名前  givenラベル:値,...

on 命令の名前 given ラベル:変数名,...
	処理する内容
end 命令の名前

 givenのあとは、レコードの書き方とほぼ同じなので解説は省きます。

 次のスクリプトは「TextEdit」の一番手前のウインドウと書類の情報を設定(変更)します(注1)。

setDoc given theText:"内容", theName:"タイトル"

on setDoc given theName:theName, theText:theText
	tell application "TextEdit"
		set name of front window to theName
		set text of front document to theText
	end tell
end setDoc

 ハンドラの中の引数はon setDoc given theName:theName, theText:theTextとして、ラベルと同じtheNametheText——を使っていますが、異なる名前に変えても構いません。

 このスクリプトでは、引数の順番を変更しても正確な値が渡されている点に注目してください。
 オブジェクトの属性を設定する感覚で使え、各引数の意味を明確に表すことができます。

ラベルで値を渡す(2)

 また、ラベル渡しの記述方法には通常の命令に近いものもあります。
 この方法で使うラベルは、あらかじめ用意されていて、その数は十数種類にのぼります(注1)。
 書式は、次のとおり。

命令の名前 ラベル 値  ラベル 値  ...

on 命令の名前 ラベル 変数名  ラベル 変数名  ...
	処理する内容
end 命令の名前

 次のスクリプトは、前置詞にatforを使っています。
 これも順番の入れ替えが可能です。
 アプリケーション命令やOSAX命令などと同じ書式なので、親しみやすいうえ、前置詞を使うことで引数の意味を明確にできます。

doToday at "公園" for "デート"
doToday at "学校" for "勉強"
doToday for "日焼け" at "海"

on doToday for toDo at thePlace
	display dialog (thePlace & "で" & toDo & "します")
end doToday

 前置詞の中でofは属性を指定する場合と紛らわしくなるので、単独では使えません。
 別の前置詞と組み合わせて使うことが前提になります。

引数の型指定

 引数にはどんなクラスの値を渡してもいいのですが、実際には命令ごとに期待される値があるのがほとんどです。
 そういう場合に、引数にクラスを指定して受け取った時点で型変換を行う書式が用意されています。
 これは引数の受け取り方によりませんが、ここでは括弧型のハンドラで説明します。

on 命令の名前(変数名 as クラス, 変数名 as クラス ...
	処理する内容
end 命令の名前

 要するに、引数の変数名にas クラスを追加してしまえば値を受け取った時点で、値のクラス変換が行われるというわけです(注1)。
 もちろんここで、変換不可能な値が渡されるとエラーが発生します。
 値が渡った時点でエラーが発生して、スクリプトの間違いに気づきやすいので、極力asを使うことをお勧めします。

squareNumber( "ENIX"  )

on squareNumber( theNumber as number )
	return theNumber * theNumber
end squareNumber

 この例では、数値に変換できない文字列なので、エラーが出ます。

結果を返す命令を作る

 結果としてさまざまな値を返す命令があるように、ハンドラの中にreturn文を入れることで利用者定義命令でも結果を返せます。  return文は前回のidleハンドラでも登場したので、既に馴染みがあると思います。

 idleハンドラでは、returnのあとの値が次にidleハンドラが呼ばれるまでの時間でした。
 しかし、idleハンドラ以外ではreturnのあとの値が結果(result)になります。

 return文を実行した時点でハンドラの実行は終了してしまうので、return文はハンドラの最後に置きます。
 書式は次のようになります。

return 結果となる値

 では、return文を使うことで本当に値が戻ってくるかどうかを確認してみましょう。

getResult()

on getResult()
	return "結果の値"
end getResult

 このスクリプトを実行すると、エディタの「結果」に"結果の値"が表示され、値が返されていることがわかります。
 ちなみに、returnのあとに何も指定しない場合は、resultはありません。
 つまり「結果」には何も表示されません。

 では、もう少し凝ったスクリプトを書いてみましょう。

getAverage({2, 10, 3, 1})

on getAverage(theList)
	set n to 0
	repeat with curItem in theList
		set n to n + curItem
	end repeat

	return (n / (number of theList))
end getAverage

 これは、リストで渡した数値の平均を求めるスクリプトです。
 決まりごとではありませんが、値を返す利用者定義命令ではgetXxx()という名前のハンドラをよく使います。
 例えば、次のハンドラは現在の秒数を返します。

getSeconds()

on getSeconds()
	return seconds of (current date)
end getSeconds

 また、ある値を引数として命令に渡して、それをなんらかのかたちに変換して値を返すハンドラは、getXxxではなくxxx2Xyyという名前がよく付けられています。
 「2」は「to」の代わりに使っているわけで、ハンドラの内容を反映する当を得たダジャレといえますが、素直にxxxToXxxとつける方がいいでしょう(注1)。

 次のハンドラは数値から各月の名称を得ます。

numberToMonth(3)

on numberToMonth(n)
	return item n of {January, February ¬
		, March, April, May, June, July, August ¬
		, September, October, November, December}	-- (注2)
end numberToMonth

局所変数(ローカル変数)

 変数は、特別に記述しない限り1つのハンドラ内で使うことが前提です。
 このような変数を局所変数(local valiable)といいます。

 ハンドラは、その中だけで処理を完結させてスクリプトの見通し(読みやすさ)をよくする役割があります。
 しかし、仮に同じ変数を複数のハンドラが使えるとなると、スクリプトの内容を把握しづらくなります。

 そこで、同じ変数は1つのハンドラの中だけでしか使えない仕組みになっています。
 言い換えれば、同じ名前の変数でもハンドラが違えば別の変数ということになります。

 変数を流用できないことに不満を感じるかもしれませんが、ハンドラを超えて値をやり取りしたい場合は引数を使えばいいので困ることはないでしょう。

 では、次のスクリプトで局所変数の有効範囲を確認してみましょう。

set x to "僕はX"
changeToXX()
display dialog x

on changeToXX()
	set x to "彼はXX"
end changeToXX

 xの値は、1行目のset x to "僕はX"で設定した"僕はX"のままです。
 このことから、別のハンドラの中にある変数は同じ名前でも、まったく別の変数であることがわかります。

 このように、ハンドラが異なると同じ名前でもまったく別の変数として扱えるので、スクリプトを書く場合に変数名をあれこれ考える必要はないわけです。
 また、スクリプトに不具合が発生した場合は、問題のあるハンドラだけをコピーして別のスクリプトとして実行できるため、原因の追究がとても楽です。

命令の行方

 AppleScriptは、スクリプト自身もオブジェクトとして扱います。
 そして、現在のスクリプトを示すものとして、meという特殊変数を用意しています。

 AppleScriptでは、オブジェクトを特に指定していない場合は、meに命令(メッセージ)を送っているものとして処理します。
 この場合は、命令のあとに暗黙のof meが付いていると考えましょう(注1)。
 しかし、tellでオブジェクトの範囲を決めている場合は、自分自身(me)に明確に命令を送ってやる必要があります。

 例えば、次のスクリプトからof meを取るとエラーになります。

tell application "Finder"
	x() of me
end tell

on x()
	display dialog "ハンドラX"
end x

 このようにtell文を使っている場合は、of meとしてスクリプト自身にメッセージを送らないと利用者定義命令を使うことはできません。

 AppleScriptは、メッセージのやり取りを中心にして動作する言語です。
 ということは、命令(メッセージ)がどのオブジェクトに伝えられるのかを常に意識しておけば、スクリプトの流れを理解しやすくなります。

 具体的には、命令(メッセージ)の種類が、AppleScriptが受け取る「AppleScript命令」、OSAXが受け取る「OSAX命令」、アプリケーションのオブジェクトが受け取る「アプリケーション命令」、ハンドラが受け取る「利用者定義命令」——のどれに該当するかを理解することです。

 利用者定義命令の使い方は、プログラムをいかにうまく作るかということと同じぐらい重要で、しかも奥が深いものです。
 今回の説明でわからない部分があっても、心配しないでください。少しずつ慣れていけば大丈夫です。

 慣れないうちは、カッコを使う利用者定義命令だけを使うことにして、ラベル渡しは仕組みだけを知っていれば十分です。
 一度に手を広げると失敗の元ですから。

【今回のまとめ】

利用者定義命令は、

  • ユーザーが作る命令
  • 新しくハンドラを作ればできあがり
  • スクリプトをわかりやすくする

引数を渡すかたちは、

  • リストと同様に順番で渡すもの
  • レコードと同様にラベルで渡すもの
  • 一般的な前置詞を使って渡すもの

利用者定義命令の注意点は、

  • 「return」で結果を返すこと
  • 他のハンドラの変数は別の変数である
  • 自分に送る命令は「my」または「of me」とする