前回までのお話
関数を定義する関数を定義しましたよ!
この関数、createLoggedFunc()
を使ってプログラムが簡略化できました。
logging APIがとりあえずの完成です。
logging API(version 0.2)の仕様説明
概要
このAPIプログラムファイルを"logging"という名前で保存したならば、このAPIを利用したい自分のプログラムファイルの先頭部分でos.loadAPI("logging")
と記述してください。
テーブル「logging」が作られて、その中にlogging APIの各関数が入っているので、logging.forward()
のように利用できます。
サンプルとしてあらかじめタートル移動系の6つの関数を用意しており、これら関数を実行するとその実行成功/失敗により異なるメッセージをログファイル(デフォルトはファイル名「mylog」)に出力します。
たとえば、logging.forward()
を実行したときに、前進成功ならば文字列 "turtle.forward()"、前進失敗ならば文字列 "-- turtle.forward()" をログファイルに出力します。
なお、任意の関数にログ出力機能を付け加えることができます(詳しくは後述)。
logging APIで利用できる関数
- turtle移動系の関数(タートルを移動させ、移動成功・失敗により異なるログを出力)
- logging.forward()
- logging.back()
- logging.up()
- logging.down()
- logging.turnRight()
- logging.turnLeft()
- ログファイルを切り替える(ログファイル名を切り替えるだけで、この関数を実行しただけでは実際にはまだ書き込まない)
- logging.changeLogfile( filename )
- 関数の実行とは関係なく直接文字列をログファイルに書き込む
- logging.write( message )
- 引数として関数を与えることで、その関数にログ機能を付け加えた新しい関数を作ることができる
- createLoggedFunc( function, succeeded_log, failed_log )
- この関数を実行すると、引数として渡した関数にログ機能をつけた新しい無名関数を返してくれる。利用例は以下のとおり。
-- ログ機能をつけた新しい関数を作る myRefuel = logging.createLoggedFunc( turtle.refuel, "succeeded!", "failed?!") -- この関数を実行する myRefuel()
ソースコード
コメント部分の英語はひどいので生暖かい目で見て下さい。
createLoggedFunc()の仕様説明
createLoggedFunc()
の引数は原則として3つとります。
- 第1引数として「関数」
- 第2引数はその関数を実行してtrueが返ってきたときに出力する「実行成功ログメッセージ」
- 第3引数はfalseが返ってきたときの「実行失敗ログメッセージ」
また、関数実行の成功失敗を問わず実行したら常に同じメッセージを出力したいときには、第3引数(failed_log)を省略してください。常に第2引数(succeeded_log)を出力します。
なおこの引数省略時の挙動は関数定義の2行目で実現しています。
failed_log = failed_log or succeeded_log
この文だけで、引数failed_logが省略されてこの中身が空(false)だったら、引数succeeded_logの中身を変数failed_logに代入するという意味になります。
引数や変数のデフォルト値を指定するのによく使う慣用表現でとても便利なので覚えてしまいましょう。
なぜこの文でこのような処理になるのか詳しく説明するには今回の記事ではさすがにスペースが足りないので、そのうち機会をとって説明する予定です*1。
話は戻りますが、なぜこのような仕様にしたかというと、
関数の中には、成功/失敗によってtrue/falseを返すものだけではなく、常にtrueしか返さないturtle.turnRight()のような関数や、そもそも成功/失敗の概念がなく返値がない関数などがあります。
このような関数では、実行したら成功/失敗を気にせず常にログを出力するようにすれば良いですね。logging APIプログラムの66・67行目のように第3引数を省略しましょう。
createLoggedFunc()の便利さをアピール
また前回の記事で作ったサブ関数であるsubDef()
と今回の関数createLoggedFunc()
を比較してみましょう。
前回のsubDef()
はわざわざ頭にlocal
をつけてローカル関数化することでユーザーに使わせないようにしているにも関わらず、今回のcreateLoggedFunc()
ではグローバル関数のままにしています。
これは、createLoggedFunc()
をユーザーに積極的に使ってもらうためです。
この関数はこのAPIの目玉関数でもあり、実際この関数だけでも非常に便利に使えます。
例を挙げましょう。
たとえば、以下のようなシンプルなプログラムがすでに作ってあるとします。
目の前のチェストからアイテムを1スタック取り出して、いろいろと動作をして、右向いて、目の前にあるチェストにアイテムを格納するというただそれだけのプログラムです。
-- functions function iroiro() -- iroiro-surudake end -- main turtle.suck() iroiro() turtle.turnRight() turtle.drop()
たとえばこのプログラム中で、関数iroiro()を実行したときだけ作業のログを取りたいと思ったらどうすればよいでしょうか?
以下のようにしましょう。
os.loadAPI("logging") -- functions function iroiro() -- iroiro siteru end iroiro = logging.createLoggedFunc(iroiro, "-- iroiro-sita!") -- main turtle.suck() iroiro() turtle.turnRight() turtle.drop()
main 部分はまったく変えていません。
プログラムファイルの先頭でlogging APIを読み込み、logging.createLoggedFun()関数を使って関数iroiro()にログ機能を追加しただけです。
つまり非常に手軽に、自分で作った関数(今回はiroiro())へログ機能を付け加えられるのです。
ログ機能が必要なくなったら、logging.createLoggedFunc()の行だけをコメントアウトすれば元通り。
タートルの行動履歴をとるという目的以外にも、デバッグ用の情報収集にも使えるかもしれません。
ね? 便利でしょ? 関数型プログラミングってすばらしい!
おわりに
関数型プログラミングの手法を用いてlogging APIを作成しその解説を行いました。
次回からは、このAPIの機能を拡張しましょう。
- これ、引数をとる関数をログ機能付きにできないんだけど、どうすればいいの?
- ログファイルを見てこれまでの移動履歴を確認し、それを逆算して元の場所に戻ってくる機能
あたりを予定に考えています。
お楽しみに。
*1:この文を完全に理解するためには、Luaの真偽値のルール、関数実行時に引数が必要な数に足りない時の処理、代入記号「=」の処理順序、論理式「A or B」の処理ルールといった色々な知識をすべて理解してなくてはならないのですよね・・・・・・。