2007-10-04
■ コンパイル時に文字列を連結する
問題
複数行の長い文字列をソースコードに行ごとに分割して書きたいが、実行時にstring-joinをするのはコストが気になる。すべての文字列の内容はコンパイル時に決まっているので、コンパイル時に文字列の連結ができないか。
答え
トラディッショナルなマクロを使うとコンパイル時に文字列を連結できます。下のjoinはコンパイル時に文字列を連結するマクロです。
(define-macro (join . strs) (string-join strs "\n"))
解説
マクロの特徴の1つはそれがコンパイル時に展開されることです。Cのマクロがプリプロセッサ(CPP)に処理されてからコンパイルされるように、Schemeのマクロはマクロ展開が行われてからその結果がコンパイルされます。従ってマクロで文字列を連結すると、コンパイラには連結された文字列が渡されます。実行時にコストがかかりません。
コンパイル結果をディスアセンブラ(disasm)で動作を確認してみます。まずは上で定義したjoinマクロを使うコードを見てみます。コンパイル時に決定した文字列が実行時にそのまま返っているのがわかります。
(disasm (lambda () (join "abc" "def"))) ;; 0 CONST-RET "abc\ndef"
一方、こちらは実行時にstring-joinで文字列を連結するコードです。実行時に文字列をスタックにプッシュし、手続きを呼び出す命令列にコンパイルされています。
(disasm (lambda () (string-join '("abc" "def") "\n"))) ;; 0 CONST-PUSH ("abc" "def") ;; 2 CONST-PUSH "\n" ;; 4 GREF-TAIL-CALL(2) #<identifier user#string-join>; (string-join '("abc" "def") "\n") ;; 6 RET
なお、コンパイラの最適化が進んで、文字列連結の定数畳み込みが行われるようになれば、こういったテクニックは不要です。従ってマクロで書けば速くて、実行時に評価されるように書くと遅いとは一概には言えません。それに、タイトなループの中でなければ文字列の連結コストは無視できる程度のはずです。仕組みを理解したうえで最適化しましょう。
参照
リファレンスマニュアル: 伝統的なマクロ、string-join
2007-10-01
■ 実行中のスクリプトファイルの名前を得る
問題
スクリプトファイル名を取得する方法は?
答え
mainに渡されるリストの第1要素がスクリプトのファイル名です。
#!/usr/bin/gosh (define (main args) (print (car args)) ; スクリプトファイル名を表示 0)
*program-name*というグローバル変数もスクリプトファイル名に束縛されているので、こちらを使ってもいいのですが、main手続きの引数はきちんとSRFI 22で定義されているので、移植性を重視するならmainの引数を使うのがよいでしょう。
参照
2007-09-27
■ 画面をクリアする
問題
端末の画面をクリアしたい。
答え
clearコマンドが返す制御文字を出力すると画面を消去できます。
(use gauche.process) ; process-output->string (define clear (let1 c (process-output->string '("clear")) (lambda () (display c)))) (clear) ; 画面を消去する
clearは実行するたびに同じ制御文字を返すので、何度も実行するのは無駄です。上のclear手続きは、コマンドを一度だけ実行してその出力をクロージャで保持しているので、そういう無駄はありません。
具体的にどのような制御文字が出力されているのかを見てみたければ、displayの替わりにwriteを使ってください。writeは制御文字をエスケープして出力します。
参照
- リファレンスマニュアル: process-output->string、let1、display、write
