文字列をキーにレコードへアクセスするもう一つの方法
- AppleScriptのレコードは、文字列をキーにアクセスできない。
- キー値は、ソースコードとして書き込んでおく必要があるのだ。
set a_record to {apple:100, |みかん|:50, |オレンジ|:150} --a_record's "みかん"--NG コンパイルさえできない
a_record's |みかん| --OK
--ちなみに、以下もできない
set a_key to |みかん| --NG "|みかん|変数は定義されていません。"
a_record's a_key
- そんな殺生な...。これでは、何をやるにも不便過ぎる...。
- coding()ハンドラを発見して、それを利用した文字列によるレコードへのアクセスは、当初以下のように実装していた。
set a_record to {apple:100, |みかん|:50, |オレンジ|:150} set a_key to "みかん"
set res to for_key1(a_record, a_key) --結果:50
on for_key1(a_record, a_key) run script coding(a_record) & "'s |" & a_key & "|"
end for_key1
on coding(obj) try
if obj's class = text then
"\"" & obj & "\""
else if obj's class = list or obj's class = record then
obj as number
else
obj as text
end if
on error msg
try
"require \"jcode\";$KCODE=\"u\";/\\{.*\\}/=~" & quoted form of msg & ";puts($&);"
do shell script "ruby -e " & quoted form of result
on error
msg's items 1 thru -24 as text
end try
end try
end coding
当初は、これでも簡潔で素晴らしい方法だと思っていたのだが、もっと効率的で素晴らしい方法があった!
もう一つの方法
- 論より証拠、まずはそのコードを見てみるのだ。
- 実行すれば、間違いなく「みかん」の50が返る。
set a_record to {apple:100, |みかん|:50, |オレンジ|:150} set a_key to "みかん"
set res to for_key2(a_record, a_key) --結果:50
on for_key2(a_record, a_key) _reader(a_key)'s value_of(a_record) end for_key2
on _reader(k) run script " on value_of(obj) obj's |" & k & "| end me"
end _reader
素晴らしい!
何が起きているのか?
良いところ
- coding()ハンドラさえ使わずに、たった50文字足らずのテキストをrun scriptするだけで、テキストによるキー値アクセスが実現できているところ。
- run scriptはコストのかかる処理なので、そこで処理するコードは短ければ短いほど、効率は良くなるのだ。
- coding()ハンドラを利用する方法では、レコードも含めてすべてをソースコードに展開して、実行していた。
- もう一つの方法では、レコードはソースコードに展開せず、objという変数で受け渡しするようにしているのだ。
AppleScriptで、こんなことができることに感動!
- サンプルコードでは、レコードは小さくて単純な作りだが、
- 実際に利用する時には、もっと巨大で複雑な構造になる。
- 巨大なレコードのソースをrun scriptするには、相当なコストがかかる。
- 一方、もう一つの方法なら、いくらレコードが巨大になっても、run scriptのコストは一定なのだ。
さらに短くシンプルに
- 仕組みは同じだが、_reader()ハンドラに分割せずにコード生成してみた。
- 依存するハンドラは可能な限り少ない方が良い。もっと言えばない方がいい。
- この方法ならfor_key()ハンドラの中ですべて完結する。
set a_record to {apple:100, |みかん|:50, |オレンジ|:150} set a_key to "みかん"
set res to for_key3(a_record, a_key) --結果:50
on for_key3(a_record, a_key) run script "
on value_of(obj)
obj's |" & a_key & "|
end
me"
result's value_of(a_record) end for_key3
パフォーマンス計測
- 1万回ループさせて、処理時間を比較してみた。
- coding()でレコードのソースに戻す方法だと、明らかに処理に時間がかかっている。
- もう一つの方法なら、5〜10倍も高速に処理される。
set a_record to {apple:100, |みかん|:50, |オレンジ|:150} set a_key to "みかん"
set t to current date
repeat 10000 times
--set res to for_key1(a_record, a_key) --140, 140, 150秒
--set res to for_key2(a_record, a_key) --32, 15, 20秒
set res to for_key3(a_record, a_key) --32, 15, 20秒
end repeat
(current date) - t
display dialog result
res
on for_key3(a_record, a_key) run script "
on value_of(obj)
obj's |" & a_key & "|
end
me"
result's value_of(a_record) end for_key3
on for_key2(a_record, a_key) _reader(a_key)'s value_of(a_record) end for_key2
on _reader(k) run script "
on value_of(obj)
obj's |" & k & "|
end
me"
end _reader
on for_key1(a_record, a_key) run script coding(a_record) & "'s |" & a_key & "|"
end for_key1
on coding(obj) try
if obj's class = text then
"\"" & obj & "\""
else if obj's class = list or obj's class = record then
obj as number
else
obj as text
end if
on error msg
try
"require \"jcode\";$KCODE=\"u\";/\\{.*\\}/=~" & quoted form of msg & ";puts($&);"
do shell script "ruby -e " & quoted form of result
on error
msg's items 1 thru -24 as text
end try
end try
end coding
キー値アクセス ハンドラの完成
- 10000回ループなので、1回当りのキー値アクセス平均コストは...
- 0.0143秒
- 0.0022秒
- どちらも極めて短い時間になるので、どうでも良いように感じるが、
- キー値アクセスのような基本的な機能は、ループの中で大量に繰り返される可能性がある。
- できる限り効率良く処理されるようにしておくべきなのだ。
- また、基本機能を提供するハンドラについては、できる限り依存するハンドラは少なく、できれば依存コードなしにしたい。
- さらに、coding()ハンドラはdo shell scriptを利用しているので、場合によっては262144バイトの制限でエラーが発生する可能性もある。
以上のことを考慮して、for_key()、set_key_value()ハンドラは以下のようになった!
set a_record to {apple:100, |みかん|:50, |オレンジ|:150}
for_key(a_record, "みかん") --結果:50
set a_record to set_key_value(a_record, "みかん", 500) a_record --結果:{apple:100, |みかん|:500, |オレンジ|:150}
set a_record to set_key_value(a_record, "バナナ", 80) a_record --結果:{apple:100, |みかん|:500, |オレンジ|:150, |バナナ|:80}
on for_key(a_record, a_key) run script "
on value_of(obj)
obj's |" & a_key & "|
end
me"
result's value_of(a_record) end for_key
on set_key_value(a_record, a_key, a_value) run script "
on set_value(obj, v)
try
set obj's |" & a_key & "| to v
obj
on error number -10006
obj & {|" & a_key & "|:v}
end try
end
me"
result's set_value(a_record, a_value) end set_key_value
参考ページ
以上の仕組みは、すべてScript factory >> XModules >> XAccessorから頂きました。感謝です!
- Script factory
- XModules
- XAccessor
- XModules
- Script factoryにはAppleScriptにまつわる、高度な技の話しが盛りだくさんで、読み始めると止まらなくなる。
- 以前から知っていたが、当時は自分の知識と経験が不足していて、その話しの価値に気付けなかった...。
今なら分かる!
- 特にXModulesのソースコードは、技の宝庫である。
- AppleScriptがこんなにも可能性がある子だったとは!
AppleScriptでこのような処理ができることを知って、またしても新たな世界が開けてしまった!
- この可能性を知ると、試してみたいこと盛りだくさんで、最近ちょっと寝る間が惜しい...。