Gauche練習帳 format関数完全マスター

だいぶGaucheに慣れてきました。今回は文字列の出力方法をこまかく指定するためのformat関数について書式を調べてみました。もともとK&R脳なのでprintfとの比較もしてみます。
Scheme言語ではprintf/sprintfのような書式指定にformat関数を使います。SRFI-28として規定されているのは、~a ~s ~% ~~ の4種類のシンプルなパターンのみなので、実用的な処理系としては独自に拡張することが必要になります。
Gaucheの場合、Common Lispを参考にformat関数の書式指定が実装されているとのこと。また汎用の多機能ライブラリSLIBをインストールするとさらに多くの書式が使えます。


Gaucheのみの場合

  • Common Lispのformatの一部(+独自拡張?)
  • パディングやカンマ挿入の機能が豊富
  • 16進数の大文字小文字が指定できる
  • 実数の書式指定はできない


Gauche + SLIB

  • Common Lispのformatのほぼフルセット
  • パディングやカンマ挿入の機能が豊富

出力先指定

format関数の第1引数を省略または#fにすると文字列として返す(sprintf)。

(format "Hello") 
(format #f "Hello") 

第1引数を#tにすると現在の出力ポートに出力(printf)。

(format #t "Hello") 

第1引数でポートを指定するとそのポートに出力(fprintf)。

(format (standard-error-port) "Hello")

フォーマット指示子の基本

フォーマット指示子は以下の順で文字を並べます。チルダと指示文字以外は省略可能なのでややこしいです。

文字列など(A,S)
文字 説明
~ チルダ
(数字) 最小幅
, (カンマ)
(数字) パディング文字の追加単位(指定個ずつ挿入)
, (カンマ)
(数字) 指定された個数のパディング文字を無条件で挿入
, (カンマ)
'(文字) パディング文字
, (カンマ)
(数字) 最大文字数
@ 右寄せフラグ
A,S 指示文字
数値(D,B,O,X,x)
文字 説明
~ チルダ
(数字) 最小幅
, (カンマ)
'(文字) パディング文字
, (カンマ)
'(文字) 区切り文字 (区切り文字出力フラグ指定時有効)
, (カンマ)
(数字) 区切り文字間隔 (区切り文字出力フラグ指定時有効)
, (カンマ)
@ プラス記号出力フラグ
: 区切り文字出力フラグ
D,B,O,X,x 指示文字
指示文字一覧
A 文字列にして出力(display)
S S式文字列にして出力(write)
D 10進出力
B 2進出力
O 8進出力
X 16進出力(大文字)
x 16進出力(小文字)

~Aや~Sは文字列出力というよりも、文字列出力といった感じ。なので渡す引数に数字やリストも指定できる。
~X/~x以外は大文字で書いても小文字で書いても機能としては同じ。

format関数の便利な点

カンマ挿入に関しては非常に柔軟で楽です。

;3桁ごとにカンマ挿入
(format "~:D" 1000000000)
;=> "1,000,000,000"

;4桁ごとにスラッシュ挿入
(format "~,,'/,4:D" 1000000000)
;=> "10/0000/0000"
その他

パディング文字はフラグなどの文字と区別できるようシングルクォーテーションでクォートする

(format "~5@D" 10)   ; クォートしないとプラス記号出力フラグと解釈
;=> "  +10"
(format "~5'@D" 10)  ; クォートするとパディング文字と解釈
;=> "@@@10"

パラメータ位置に「V」を指定すると引数からパラメータを取得する(printfの%*)
~%は改行を示す(printfの\n)
~~は~自身を示す(printfの%%と同様)

~* 指定個数の引数を無視する

(format "~D ~* ~D" 10 11 12)
;=> "10  12"
(format "~D ~2* ~D" 10 11 12 13)
;=> "10  13"

printfとの対応

printfでよく使うパターンをformatで書いてみるとこんな感じ。

引数: s:文字列 i:整数 f:実数

意味 printf format
文字列 "%s", s "~A" s
文字列幅指定右詰 "%5s", s "~5@A" s
文字列幅指定左詰 "%-5s", s "~5A" s
幅を引数で指定 "%*s", 5, s "~VA" 5 s
整数 "%d", i "~D" i
整数正符号付 "%+d", i "~@D" i
整数幅指定右詰 "%5d", i "~5D" i
整数幅指定右詰0埋め "%05d", i "~5,'0D" i
整数幅指定左詰 "%-5d", i "~5A" i
16進数幅指定右詰0埋め "%05X", i "~5,'0X" i
16進数幅指定右詰0埋め小文字 "%05x", i "~5,'0x" i
実数 "%f", f "~D" f
実数幅指定右詰 "%5f", f なし
実数幅指定右詰小数部桁指定 "%5.2f", f なし

工夫次第でいろいろできる

16進数の左詰はformatを2個使うとできる。

(format "~8A" (format "~4,'0X" 255))
;=> "00FF    "

SLIBを使うほどじゃないし、簡単に小数部の桁数を指定したい場合はこんな感じだろうか

(define (fwid width num)
 (let ((w (expt 10 width)))
   (format "~D.~D"
     (truncate->exact num)
     (abs (- (truncate->exact (* w num))
          (* (truncate->exact num) w))))))

(define num 1.5)
(fwid 4 num)  ; 小数点以下4桁指定
;=> "1.5000"

実数の書式をこまかく指定するときはやっぱりSLIB

(use slib)
(require 'format)
(format "~6,2F" 1.5)  ; 注:6,2はピリオドじゃなくてカンマ!
;=> "  1.50"

(おまけ)彼氏がprintf使ってた。別れたい…

実はSLIBを入れるとGaucheでもprintfが使えるようになります。

(use slib)
(require 'printf)
(printf "%6.2f" 1.5)
;-> "  1.50"
;=> 6         ; 戻り値は出力文字数

かつてCからC++に乗り換えたとき「もうprintfは使わない」と思ったのに、いまだに手放せません。まあC、C++はもちろんシェルスクリプトAwkPerlRubyとか、最近はJavaなんかでも使えるから、書式指定の共通言語としてprintfは良くも悪くも生き残るでしょうね。