Next: プログラムのロード, Previous: eval と repl, Up: 組み込みライブラリ [Contents][Index]
• ポート: | ||
• ポートとスレッド: | ||
• ポート共通の操作: | ||
• ファイルポート: | ||
• 文字列ポート: | ||
• コーディング認識ポート: | ||
• 入力: | ||
• 出力: |
Portは、Schemeにおいて抽象化された入出力のインタフェースを提供します。 Gaucheはportにいくつかの拡張を行い、いろいろなアプリケーションに対応できるようにしました。
R7RSはテキストポートとバイナリポートを定義しています。 Gaucheでは、ほとんどのポートはテキストI/OとバイナリI/Oを混ぜて使えます。 概念的には、異なる型のオブジェクトのソース/シンクは別物と考える方が綺麗ですし、 ひとつのポートでテキストI/OとバイナリI/Oを混在して使うことは滅多にありません。
しかし現実には、ポートを型の無いデータの集まりにつながったものとして、
データをどう解釈するかは後から決めたい、という場合があります。
例えば標準入出力です。Unix的な環境では、あらかじめオープンされている標準入出力
に対してテキストI/Oを行うかバイナリI/Oを行うかはプログラムに任されています。
R7RSでは、current-input-port
等の初期値はテキストポートであると
規定しています。Gaucheではどちらにも使うことができます。
ポートはまた、データストリームを変換するのにも使えます。例えばgauche.charconv
モジュールでは、文字コード間の変換を行うポートを提供しています
(詳しくは文字コード変換を参照)。
また、特殊な機能を実現するポートもあります。 コーディング認識ポート(コーディング認識ポート参照)は ファイル中の特殊なコメントを認識して、そのファイルがどの文字エンコーディング で書かれているかを検出します。 仮想ポート(仮想ポート参照)はSchemeでふるまいをプログラムできる ポートを提供します。
GaucheがスレッドサポートをONにしてコンパイルされている場合、
組み込みのポート操作関数はポートをロックして、
複数のスレッドからの同一のポートへのアクセスがポートの内部状態を壊さないように
しています。
(SRFI-18によって要求されている動作です)。
ここで「組み込みのポート操作関数」はGaucheにより提供される、
ポートを引数に取り何らかのI/O動作や問い合わせを行う手続きで、
read
/write
、read-char
/write-char
、
port->string
等を含みます。
但し、call-with-*
やwith-*
系関数は、
与えられた手続きを呼ぶ際にはポートをロックしません。
その手続きが別のスレッドにポートを渡すかもしれず、Gaucheにはそれを知ることが
できないからです。
従って、マルチスレッド環境でポートへのアクセス競合により ポートの内部状態を壊してしまうんじゃないか、などとあまり神経質に なる必要はありません。但し、このロック機構はあくまで予想外の アクセス競合によってポートがおかしな状態になってしまうことを 防ぐための安全ネットであって、一般的な排他制御機構として使われる ことは想定していないのに注意して下さい。このロックの実装は、 ポートへのアクセス競合は例外的な場合のみであると仮定し、 通常のアクセスにおけるオーバヘッドを避けるために、スピンロックを 使用します。もし、意図的にポートアクセスが競合するようなコードを書く場合は、 明示的に排他制御をしてください。
portをロックし、thunkを実行します。 ロックはthunkのダイナミックエクステントの期間有効です。
portがロックされている期間での組み込みのポートアクセス関数の 呼び出しは排他制御をバイパスするため、性能向上が見込まれます。
ロックの有効期間はthunkのダイナミックエクステントなので、
thunk内からwith-port-locking
の外で捕捉された
継続を呼んだ場合、ロックは解放されます。その後、thunk内で
捕捉された継続が呼ばれた場合、再びロックが獲得されます。
with-port-locking
はネスト可能です。ロックは最も外側の
with-port-locking
の期間中有効となります。
この手続きはポート組込みのロック機構を利用します。つまり、ポートアクセスが 競合した場合はbusy waitになるということです。この手続きはあくまで 頻繁なロックによるオーバヘッドを回避するためのものです。 もし本当に競合が予測される場合は明示的に排他制御を行ってください。
[R7RS base]
obj がそれぞれポート、入力ポート、出力ポートなら真を返します。
port?
はR5RSの"Standard Procedures"の項には
載っていませんが、"Disjointness of Types"の項に挙げられています。
objがポートであり、既に閉じられていた場合に真を返します。 一度閉じたポートは再び開くことはできません。
[R7RS base] 現在の入力ポート、出力ポート、エラー出力ポートをそれぞれ返します。
R7RSではこれらのポートの初期値はテキストポートであると規定されています。 Gaucheでは、これらのポートの初期値はテキスト入出力もバイナリ入出力も扱えます。
現在のポートの値はparameterize
を使って一時的に変更できます
(パラメータ参照)。もっとも、典型的な場合には
より簡単なwith-output-to-string
やwith-input-from-file
などの
手続きが使えるでしょう。
(use gauche.parameter) (let1 os (open-output-string) (parameterize ((current-output-port os)) (display "foo")) (get-output-string os)) ⇒ "foo"
デバッグトレース出力に使われる出力ポートを保持するパラメータです。
初期値はcurrent-error-port
の初期値と同じです。
debug-print
機能(デバッグ補助参照)と、
マクロトレース機能(マクロ展開をトレースする参照)がこのポートを使っています。
プログラム開始時点の標準入出力ポートを返します。これらの値が
current-input-port
、current-output-port
、
current-error-port
のデフォルトとなります。
これらの手続きの値もparameterize
で変えることができますが、
(1) current-*-port
はプログラム実行前に初期化されるので、
プログラム中でstandard-*-port
を変更しても影響は及ばない、
(2) これらの手続きの値の変更はScheme世界だけのことで、低レベルライブラリが
参照しているシステムのstdioファイルディスクリプタは変更されない、
という点に注意してください。
それぞれ入力、出力、エラーポートをportにセットした状態でthunkを呼び出します。
port
はthunk実行後にも閉じられないことに注意してください。
上の3つの動作を同時に行う手続きです。
入力、出力、エラーの各ポートをそれぞれiport, oport, eportに
セットしてthunkを呼び出します。変更する必要がないポートの引数には
#f
を渡すことができます。
port
はthunk実行後にも閉じられないことに注意してください
(最近のScheme標準は良く似た名前の手続きcall-with-port
を追加しましたが、
生憎そちらはポートを閉じる仕様になっています。call-with-port
については
下で説明します。)
[R7RS base]
ポートを閉じます。close-port
は入力ポートも出力ポートも閉じられますが、
close-input-port
とclose-output-port
はそれぞれ対応するポートのみに
使え、異なる種類のポートが渡されたらエラーを通知します。
理屈の上では、close-port
だけあれば十分です。3つ手続きがあるのは歴史的理由です。
R5RSにはclose-input-port
and close-output-port
だけがありました。
R6RSとR7RSは3つ全てをサポートしています。
[R7RS base]
procを、portを引数として呼び出します。procからリターンするか、
エラーが投げられた場合はportがクロースざれます。
procが返す値がそのままcall-with-port
の返り値となります。
portのタイプを、シンボルfile
、string
、proc
の
いずれかで返します。
portの名前を返します。ポートがファイルに関連付けられている場合は、ポートの名前は ファイル名です。そうでない場合、ポートを説明する文字列が返されます。
ファイルポート((port-type port)
がfile
を返すもの)
に対して、そのバッファリングモードを読みだし、もしくは変更します。
入力ポートではバッファリングモードは
:full
、 :modest
、:none
のいずれかです。
出力ポートでは
:full
、 :line
、:none
のいずれかです。
バッファリングモードの詳細な説明は、ファイルポート を参照してください。
port-buffering
がファイルポート以外のポートに対して呼ばれた場合は
#f
を返します。port-buffering
のsetterが
ファイルポート以外のポートに対して呼ばれた場合はエラーとなります。
portの現在の行番号を返します。行番号は、ファイルに関連付けられたポートで かつシーケンシャルなキャラクタI/Oを行っている場合のみ有効です。それ以外の場合は -1を返します。
portがファイルに関連付けられている場合、そのファイルディスクリプタ番号を
返します。それ以外の場合は#f
を返します。
dup?引数に真の値が与えられると、この手続きはdup(2)
を呼んで
複製したファイルディスクリプタを返します。この場合、返されたファイルディスクリプタは
呼び出し側が責任を持って閉じなければなりません (sys-close
が使えます)。
[SRFI-192]
portの現在の位置を返します。現在の位置とは、
入力ポートでは次の読み込みでデータが読まれる位置、
出力ポートでは次の書き込みでデータが書かれる位置です。
(peek-char
/peek-byte
では現在の位置は動きません)。
portが単純なファイルポートや文字列ポートなら、
ポートの位置はストリームの先頭からのバイトオフセットを表す正確な整数です。
より複雑なポート、例えば文字エンコーディング変換ポートでは、
ポート位置を取ることはできないか、取れた場合でもその値はバイトオフセットに対応するとは
限りません。
仮想ポート(仮想ポート参照)では、現在位置は任意のSchemeオブジェクトで、
set-port-position!
に渡す以外に使うことはできません。
portがポート位置をサポートしていなければエラーが投げられます。
ポート位置をサポートしているかどうかはport-has-port-position?
で調べられます。
ポータブルなコードを書くときは、以下のsrfi-192の条件に気をつけてください。
set-port-position!
するのにだけ使うことができます。
[SRFI-192]
ポートがport-position
手続きをサポートしていれば#t
を、
そうでなければ#f
を返します。
[SRFI-192] posをportの現在の位置としてセットします。 現在の位置とは、 入力ポートでは次の読み込みでデータが読まれる位置、 出力ポートでは次の書き込みでデータが書かれる位置です。
posの解釈はportに依存します。一般的には、
portに対するport-position
で以前返されたオブジェクトしか渡せません。
ただし、portが単純なファイルポート(ファイルに対してオープンされ、
文字コード変換などを行っていないポート)や入力文字列ポートならば、
非負の正確な整数をバイトオフセットとして渡すことができます。
ポートが位置のセットを許さない場合や、posが位置として不正な場合は
エラーが投げられます。portに位置がセットできるかどうかは
port-has-set-port-position!?
で検査できます。
[SRFI-192]
ポートがset-port-position!
手続きをサポートしていれば#t
を、
そうでなければ#f
を返します。
(この手続きは非推奨です。新しいコードにはport-position
および
set-port-position!
を使ってください)。
portがランダムアクセス可能なポートの場合、
この手続きはportのread/writeポインタをoffsetとwhenceの値によって
設定し、新たなread/writeポインタの値(データの先頭からのバイトオフセット)を
返します。portがランダムアクセス可能でない場合は#f
が返されます。
現在のバージョンでは、ファイルポートおよび入力文字列ポートがランダムアクセス可能です。
出力文字列ポートは現在のポインタの値を問い合わせる動作だけが可能です。
ポートのポインタはバイト数で表現され、文字数とは異なることに注意して下さい。
portが出力ファイルポートの場合は、データの終端を超えた位置までseek することが可能です。その場合の動作はPOSIXのlseek(2)に準じます。 入力ファイルポートや入力文字列ポートではデータの終端以降にseekすることはできません。
whence引数は、offsetの基準を指定する小さな整数です。 以下の定数が定義されています。
SEEK_SET
offsetはデータ先頭からのバイト数を指定します。 whenceが省略された場合のデフォルトの動作です。
SEEK_CUR
offsetは現在のread/writeポインタからの相対バイト数を指定します。 offsetが0であれば、ポインタを動かさずに現在のポート位置を知ることができます。
SEEK_END
offsetはデータの終端からの相対バイト数を指定します。
portの現在のread/writeポインタの値をバイト数で返します。
portがランダムアクセス可能でない場合は#f
が返されます。
これは以下の呼び出しと等価です。
(port-seek port 0 SEEK_CUR)
名前に関するメモ: port-seek
は他の処理系で
seek
、file-position
、input-port-position
/
output-port-position
等と呼ばれています。
port-tell
はtell
、ftell
、set-file-position!
等と
呼ばれています。いくつかの処理系はport-position
という手続きを
持っていますが、port-seek
とは別の機能を実現しています。
file-position
はCommonLisp由来の名前ですが、
fileポート以外のものも扱うため採用しませんでした。
また、seek
とtell
はPOSIXの名前由来であり、
Gaucheの名前付け規則を使ってsys-seek
とsys-tell
としても
よさそうですが、portの操作はシステムコールレベルよりも抽象度が高いため
これも採用しませんでした。結局、新しい名前を採用することにしました。
srcからEOFまでデータを読みだし、dstへ書き出します。
キーワード引数unitは0以上の整数か、シンボルbyte
もしくはchar
でなければなりません。これはデータをコピーする単位を指定します。
整数ならば、その大きさ(0の場合はシステム規定の大きさ)のバッファが確保され、
ブロックI/Oを使って転送が行われます。通常のファイルをコピーする場合などはこれが
速いでしょう。もしunitがシンボルbyte
であれば、バイト毎
に読みだし/書き込みが行われます。unitがシンボルchar
であれば、
キャラクタ毎に読みだし/書き込みが行われます。
キーワード引数sizeに非負の整数が与えられた場合、それはコピーされるデータの
最大量を指定します。unitがシンボルchar
の場合はsizeは
コピーされる文字数を、そうでない場合はバイト数を指定します。
unitがシンボルchar
の場合はコピーされた文字数を返し、
そうでない場合はコピーされたバイト数を返します。
[R7RS+] ファイルfilenameを入力または出力用にオープンし、 入力ポートまたは出力ポートを作成して返します。
キーワード引数により、動作を細かく指定できます。
:if-exists
このキーワード引数はopen-output-file
のみに指定でき、
filenameが既に存在した場合の動作を指定します。次の値のいずれかを与えることができます。
:supersede
既存のファイルが長さ0に縮められます。これが既定の動作です。
:append
既存のファイルにこれから書き出す内容が追加されます。
:overwrite
既存のファイルにこれから書き出す内容が上書きされます。 書き出されるデータが既存のファイルのデータよりも短い場合、 残りの部分はそのまま残されます。
:error
エラーが報告されます。
#f
何もせず、#f
を返します。
:if-does-not-exist
このキーワード引数はfilenameが存在しない場合の動作を指定します。
:error
エラーを報告します。これがopen-input-file
の既定の動作です。
:create
ファイルが作成されます。これがopen-output-file
の既定の動作です。
ファイルの存在のチェックと作成はアトミックに行われます。
このオプションに加え、if-existsオプションに:error
か#f
を
指定することで、排他的にファイルを作成することができます。
open-input-file
に対してはこの値を指定することはできません。
#f
何もせず、#f
を返します。
:buffering
この引数はバッファリングモードを指定します。以下の値が設定できます。
ポートのバッファリングモードは手続きport-buffering
(ポート共通の操作参照)によって
読みだし/変更可能です。
:full
出来る限りデータをバッファリングします。これがデフォルトのモードです。
:none
バッファリングを行いません。出力ポートにデータが書き出されるか、 入力ポートからデータが読み込まれる度に、下位にあるシステムコールが呼ばれます。 プロセスの標準エラーポートはこのモードでオープンされています。
:line
このモードは出力ポートにのみ有効です。書き出されたデータはバッファに 貯められますが、改行文字が書かれたらフラッシュされます。 このモードは対話的な出力ポートなどに便利です。 プロセスの標準出力ポートはこのモードでオープンされています。 (これは、Cのstdioライブラリの「ラインバッファリング」とちょっと違うことに 注意してください。stdioでは同じファイルディスクリプタから入力が行われる時も バッファはフラッシュされますが、Gaucheではそうはなりません)。
:modest
このモードは入力ポートにのみ有効です。ほとんど:full
バッファリングモードと
同じですが、read-uvector
はポートに要求されたデータより少ないデータしか
無かった場合、要求された量がたまるまで待つのではなく、今あるデータだけを
返します(:full
の場合はread-uvector
はすべてのデータが到着するまで
待ちます)。このモードはポートがパイプやネットワークに接続されている場合に
便利です。
:element-type
この引数はファイルのタイプを指定します。
:binary
ファイルはバイナリモードでオープンされます。(これがデフォルトです。)
:character
ファイルはキャラクタモード(テキストモード)でオープンされます。
註: このフラグで違いが出るのはWindowsネイティブ版のみです。
キャラクタモードでは、#\newline
文字をを出力するとCR + LFが書き出され、
またCR + LFシーケンスは単一の#\newline
として読まれます。
Unixでは両者に違いはありません。
なお、Gaucheにはテキストポートとバイナリポートの区別はありません。 したがってこのフラグはWindowsの行終端文字の扱いのみのためにあります。
:encoding
この引数はファイルの文字エンコーディングを指定します。引数は文字列かシンボルで、 文字エンコーディングスキーム(CES)の名前を渡します。
open-input-file
では、ここにワイルドカードCES (例: *jp
) を
渡して、入力ファイルのエンコーディングを推測させることもできます
(文字エンコーディングの自動判定参照)。
また、ここに#t
を渡すと、入力ポートはCoding aware portでラップされます
(コーディング認識ポート参照)。入力ファイルがエンコーディング指定の
マジックコメントを持っていることが期待できる場合に便利です。
この引数が与えられた場合、Gaucheは自動的にgauche.charconv
モジュールを
ロードし、ポートの入出力時に文字コード変換を行います。
CESについて詳しくはサポートされる文字エンコーディングを参照してください。
:conversion-buffer-size
この引数は、文字エンコーディング変換に使うバッファサイズを指定するために encoding引数と共に使うことができます。渡された値はそのまま 文字コード変換ポートのコンストラクタのbuffer-size引数に渡されます (変換ポート参照)。
この引数を指定する必要は滅多にありませんが、入力ファイルの文字エンコーディングを 推測しなければならない場合、大きめのバッファサイズの方が精度が上がります。 推測ルーチンがより多くのデータを見て文字エンコーディングを決定できるからです。
:conversion-illegal-output
この引数はencode引数と一緒に使うことで、
文字エンコーディング変換において、元の文字に対応する文字が変換先に無い場合の振る舞いを
指定できます。値はシンボルraise
かreplace
でなければならず、
それはそのまま変換ポートのillegal-output引数に渡されます。
詳しくは変換ポートを参照してください。
if-existsとif-does-not-existフラグの組合せにより、 色々な動作を実現できます。
(open-output-file "foo" :if-exists :error) ⇒ ;"foo"を排他的にオープンするかエラーを報告する (open-output-file "foo" :if-exists #f) ⇒ ;"foo"を排他的にオープンするか#fを返す (open-output-file "foo" :if-exists :append :if-does-not-exist :error) ⇒ ;"foo"が既に存在する場合に限り、それを追加モードでオープン
ファイルをオープンせずにその存在をチェックするには、
sys-access
かfile-exists?
を使って下さい (ファイルの状態参照)。
移植性に関する註:Schemeシステムによっては、filenameのところに
シェルコマンドを指定して、サブプロセスの標準入出力と通信できるようにするものが
あります。他のスクリプティング言語(例:Perl)にも同様の機能があります。
Gaucheでは、open-input-file
とopen-output-file
は
あくまでファイル (OSがファイルとして扱うもの) のみに対して使えます。
サブプロセスと通信するためには、「プロセスポート」という機能が提供されています。
Process portsを参照して下さい。
[R7RS+] stringで示されるファイルを入力または出力用にオープンし、 作成されたポートを引数として手続きprocを呼び出します。 procが正常終了するか、proc内で捕捉されないエラーが起きた場合に ファイルはクローズされます。
キーワード引数は
open-input-file
及びopen-output-file
のものと同じ意味を持ちます。
if-existsやif-does-not-existに#f
を指定した場合、
ファイルがオープンされなかった場合はprocにポートではなく#f
が渡される
ことに注意して下さい。
procが返す値を返します。
[R7RS file] stringで示されるファイルを入力または出力用にオープンし、オープンされた ポートを現在の入力または出力ポートに設定して、thunkを呼び出します。 thunkが戻るか、thunk内で捕捉されないエラーが生じた際にファイルは閉じられます。
thunkが返す値を返します。
キーワード引数は
open-input-file
及びopen-output-file
のものと同じ意味を持ちます。
但しif-existsやif-does-not-existに#f
が指定され、
ファイルがオープンできなかった場合は、thunkは呼ばれずに
直ちに#f
が返されます。
ポートを閉じるセマンティクスについて:
R7RSはcall-with-port
等の説明において、次のように述べています。
「procが戻って来なかった場合、今後ポートが読み書きに一切使われないことが
証明できない限りは、ポートは自動的には閉じられない」。
Gaucheの実装は若干この条件には反しています。捕捉されないエラーがprocから 発せられたというだけでは、そのポートが今後一切使われないかどうかはわかりません。 しかし実際には、そのようなエラーが発せられた後でポートに対して意味のある操作をするのは 非現実的です。ポートがどのような状態にあるかわからないわけですから。 現実的なプログラムでは、ポートに対して意味のある操作をしつづけたいのなら、 procの中で明示的にエラーをハンドルすべきでしょう。
call-with-input-file
の外で捕捉された継続をproc内で呼んだ場合には
ポートは閉じられないことに注意して下さい。後でprocへと制御が戻ってくるかも
しれないからです (コルーチン等)。また、
低レベルの例外メカニズム(例外の処理
参照)を利用した場合、エラー時にポートを閉じるのはプログラマの責任になります。
与えられたファイルディスクリプタにアクセスする入力または出力ポートを
作成して返します。buffering はopen-input-file
の項で
説明されたポートのバッファリングモードを指定します。デフォルトは:full
です。
nameはport-name
によって返されるポートの名前を指定します。
owner? は、このポートを閉じた時にfdもクローズすべきかどうかを
指定します。#f
ならポートを閉じてもfdはそのままです。
#t
ならポートを閉じた時にfdも自動的に閉じられます。
また、シンボルdup
を渡すこともできて、その場合は内部でfdが
dup(2)
システムコールによって複製され、新しいfdをポートが所有します。
新たなfdはポートが閉じると自動的に閉じられますが、fd自体は閉じられません。
また、fdをポートとは独立して閉じることができます。
システムのdup2(2)
のインタフェースです。
アトミックにtoportのファイルディスクリプタをクローズし、fromportの
ファイルディスクリプタを複製したものをtoportに設定します。
toport、fromportはいずれもファイルポートでなければなりません。
ファイルディスクリプタが「複製」されると、ふたつのディスクリプタ番号が異なっていても
それらはシステムのオープンファイルテーブルの同じエントリを指します。
例えば、現在の(システムレベルでの)ファイル上の読み書き位置は共有されます。
port-fd-dup!
の後で、port-seek
をfromportに
対して呼び出せば、その変更はtoportの読み書き位置にも影響を与えるでしょうし、
その逆もまたあります。ただし、共有されるのはシステムレベルの情報のみで、
toportやfromportがバッファリングされている場合、バッファの内容は
共有されません。
この手続きは、主にファイルディスクリプタを明示的に制御する必要のあるプログラム
のために用意されています。例えばデーモンプロセスがその入出力を/dev/nullなどの
無難なデバイスに切り替えたり、シェルプロセスが子プロセスをexec
する前に
そのファイルディスクリプタをセットアップしたりするような場合です。
Next: コーディング認識ポート, Previous: ファイルポート, Up: 入出力 [Contents][Index]
文字列ポートは、メモリ上のデータと関連付けられたポートです。
[R7RS base][SRFI-6]
stringを内容とする入力文字列ポートを作って返します。
文字列に逐次的にアクセスする場合、インデックスをインクリメントしながら
string-ref
を呼び出すより効率の良い方法です。
(define p (open-input-string "文字 列")) (read p) ⇒ 文字 (read-char p) ⇒ #\space (read-char p) ⇒ #\列 (read-char p) ⇒ #<eof> (read-char p) ⇒ #<eof>
nameキーワード引数はGaucheの拡張です。
デフォルトでは、作られるポートの名前は(input string port)
になります。
ポートの名前は主にデバッグのために使われます。この引数で代わりの名前を指定できます。
Gaucheの慣習として、ファイルポートは元ファイルのパス名を名前に持つので、
ポート名にデバッグ用途の情報を載せる場合はパス名と区別しやすいよう括弧に入れるのが良いでしょう。
gosh> (open-input-string "") #<iport (input string port) 0x215c0c0> gosh> (open-input-string "" :name "(user input)") #<iport (user input) 0x22a4e40>
portは入力文字列ポートでなければなりません。 入力ポートに残っている文字列を返します。 portの内部ポインタは動かされないので、portに対するreadは 影響を受けません。portが既にEOFに達していた場合は、空文字列が返されます。
(define p (open-input-string "abc\ndef")) (read-line p) ⇒ "abc" (get-remaining-input-string p) ⇒ "def" (read-char p) ⇒ #\d (read-line p) ⇒ "ef" (get-remaining-input-string p) ⇒ ""
[R7RS base][SRFI-6]
出力文字列ポートを作成して返します。このポートに書き出された文字列は
内部のバッファにたくわえられ、get-output-string
で取り出すことが
できます。
これは、順番に文字列を構成する方法として、あらかじめ文字列をアロケートして
string-set!
で埋めて行くよりもずっと効率の良い方法です。
nameキーワード引数はGaucheの拡張です。
デフォルトでは、作られるポートの名前は(output string port)
になります。
ポートの名前は主にデバッグのために使われます。この引数で代わりの名前を指定できます。
Gaucheの慣習として、ファイルポートは元ファイルのパス名を名前に持つので、
ポート名にデバッグ用途の情報を載せる場合はパス名と区別しやすいよう括弧に入れるのが良いでしょう。
gosh> (open-output-string) #<oport (output string port) 0x22a4c00> gosh> (open-output-string :name "(temporary output)") #<oport (temporary output) 0x22a49c0>
[R7RS base][SRFI-6]
出力文字列ポートport
を取り、それまでそのポートに蓄積された
文字列を返します。バイトデータがそのポートに書き出されていた場合、
この手続きはまず内部バッファをスキャンし、結果が完全な文字列で表現できるかどうかを
調べます。もし表現できなければ、不完全な文字列が返されます。
これはportの操作には影響をあたえません。get-output-string
を
呼んだ後でも、portに内容を蓄積しつづけることができます。
これらのユーティリティ関数は次に定義されるような動作をします。 インタフェースはファイルポートを扱う類似の関数と揃えてあります。
(define (call-with-output-string proc) (let ((out (open-output-string))) (proc out) (get-output-string out))) (define (call-with-input-string str proc) (let ((in (open-input-string str))) (proc in))) (define (with-output-to-string thunk) (let ((out (open-output-string))) (with-output-to-port out thunk) (get-output-string out))) (define (with-input-from-string str thunk) (with-input-from-port (open-input-string str) thunk))
(define (call-with-string-io str proc) (let ((out (open-output-string)) (in (open-input-string str))) (proc in out) (get-output-string out))) (define (with-string-io str thunk) (with-output-to-string (lambda () (with-input-from-string str thunk))))
文字列ポートを使う定型句をユーティリティ関数にしました。
(write-to-string obj writer) ≡ (with-output-to-string (lambda () (writer obj))) (read-from-string string) ≡ (with-input-from-string string read)
writerの既定値はwrite
です。start, endは
省略されればそれぞれ0と文字列の長さになります。
移植性への註:Common Lispに同名の関数があります。必須引数の動作は同じですが、
省略可能な引数は異なります。
STkにはread-from-string
がありますが、省略可能な引数は取りません。
コーディング認識ポートは特殊な手続的入力ポートで、load
が
プログラムソースコードを読む際に使われています。このポートは
;; -*- coding: utf-8 -*-
のような、プログラムソースの
文字エンコーディングを指定する特殊なコメントを認識し、適切な
文字エンコーディング変換を行います。
特殊なコメントでソースの文字エンコーディングを指定することについては、
マルチバイトスクリプトを参照して下さい。
入力ポートを引数としてとりコーディング認識入力ポートを返します。 基本的には iport からの入力データをリーダにわたしているだけです。 しかし、iport からの入力データの最初の2行以内に、特別な呪文コメント が現れた場合、コーディング認識ポートは、その後に読み込まれるデータについて 必要な文字エンコーディング変換を行います。
引数として渡されたポート、iport は生成されたコーディング認識 ポートによって所有されます。つまり、コーディング認識ポートがクローズ されると、iport もクローズされます。iport から読み込まれた 内容はコーディング認識ポート内でバッファリングさます、したがって、 別のコードで iport から読み出しを行うべきではありません。
デフォルトでは、Gauche の load
はプログラムソースを読むのに
コーディング認識ポートを使います。したがって、文字エンコーディングを
示す特別な呪文コメントは、Gauche のソースプログラムでは有効になります
(Schemeファイルのロード参照)。ただし、この機構自身は load
とは
独立しており、このポートを別の目的で利用できます。特にコーディングの
呪文コメントがある Scheme のソースプログラムを処理する関数を書くときに
便利です。
Next: 出力, Previous: コーディング認識ポート, Up: 入出力 [Contents][Index]
入力に関する手続きで、省略可能な引数iportは入力ポートでなければなりません。 省略された場合が現在の入力ポートが使われます。
• データの読み込み: | ||
• リーダー字句モード: | ||
• 読み込み時コンストラクタ: | ||
• 入力ユーティリティ手続き: |
[R7RS base] iportからS式をひとつ読み込んで返します。 GaucheはR7RSに定義されている構文要素に加え、字句構造に 定義されている拡張構文要素を認識します。
iportが既にEOFに達していた場合は、EOFオブジェクトが返されます。
この手続きはS式を構成する最後の文字までを読み、その後の文字はポートに
残します。これは、S式に続く空白文字も読み込むCommonLispのread
の
振る舞いとは異なります。
[SRFI-38]
これらの手続きは、SRFI-38 で定義されていて、共有構造を表す記法
(#n=
, #n#
)を認識できます。Gauche の組み込み read
は
この SRFI-38 の記法を認識します。それゆえ、これらの手続きは、read
と同じで、SRFI-38 との互換性のために用意されています。
[R7RS base] iportから1文字読み込んで返します。 iportが既にEOFに達していた場合はeofオブジェクトを返します。 iportにあるバイトストリームが正しい文字を構成しない場合、 ふるまいは未定義です。(将来はポート側に、不正な文字に対する対応を決める オプションを設ける予定です)。
[R7RS base] iportから1文字読み込んで返します。文字はそのままiportに留まります。 iportが既にEOFに達していた場合はeofオブジェクトを返します。 iportにあるバイトストリームが正しい文字を構成しない場合、 ふるまいは未定義です。(将来はポート側に、不正な文字に対する対応を決める オプションを設ける予定です)。
[R7RS base] 入力ポートiportから1バイト読み込み、0から255までの整数値として返します。 iportが既にEOFに達していた場合はeofオブジェクトを返します。
これは伝統的にread-byte
と呼ばれてきましたが、
R7RSではread-u8
と名づけています。どちらも使えます。
[R7RS base] 入力ポートiportの先頭の1バイトを見て、それを0から255までの整数値として返します。 iportが既にEOFに達していた場合はeofオブジェクトを返します。
これは伝統的にpeek-byte
と呼ばれてきましたが、
R7RSではpeek-u8
と名づけています。どちらも使えます。
[R7RS base] 入力ポートから、行末もしくはEOFまで読み込んで文字列として返します。 よく使われる行末 (LF only, CRLF, and CR only) を認識します。 戻り値にはこれらの行末文字は含まれません。 iportが既にEOFに達していた場合はeofオブジェクトを返します。
iportから、内部文字エンコーディングでは文字を構成し得ないバイトシーケンスが
読まれた場合、デフォルトではread-line
はエラーを通知します。
しかし、省略可能な引数allow-byte-string?に真の値が与えられた場合は、
read-line
はエラーを通知せず、かわりにバイト文字列 (不完全な文字列) を
返します。この動作は、特に文字エンコーディングが不明なソースから読み込む際に
便利です。例えばXMLドキュメントを読み込む際、最初の行のcharsetパラメータを
チェックしてから適切な文字エンコーディング変換ポートを使うといった用途などです。
この動作はGauche独自の拡張です。
[R7RS base] nchars文字を読み込み、もしくはそれ以前にEOFに達したら読めただけの文字を 使って文字列を作って返します。もし入力が既にEOFに達していた場合は EOFを返します。
この手続きは非推奨になりました。read-uvector
を使ってください
(ユニフォームベクタのブロック入出力参照)。
nbytesバイトのデータをiportから読み込み、 不完全な文字列として返します。iportに十分なデータが無い場合、 返される文字列はnbytesより短いかもしれません。 nbytesが0の場合は、常に空文字列が返されます。
iportが既にEOFに達していた場合はEOFオブジェクトが返されます。
iportがファイルポートだった場合、read-block
は
ポートのバッファリングモードによってふるまいが異なります
(バッファリングモードの詳細についてはファイルポートを参照して下さい)。
:full
の場合、read-block
は
EOFを読まない限り、nbytesのデータが読まれるまで待ちます。
:modest
か:none
である場合、
read-block
はEOFが読まれなくても、すぐに読めるデータがnbytesよりも
少ない場合はそのデータだけを返します。
データブロックをポートに書き出すには、データが文字列で表現されている
場合は単純にdisplay
が使えます。データがuniform vectorで表現されている
場合はgauche.uvector
モジュールのwrite-uvector
が
使えます (ユニフォームベクタのブロック入出力参照)。
[R7RS base] EOFオブジェクトを返します。
[R7RS base]
objがEOFオブジェクトなら#t
を返します。
[R7RS base]
portから文字が読み出せる状態ならば#t
を返します。
今のところ、この手続きはportから少なくとも1バイト読み出せる状態なら#t
を返します。そのバイトがマルチバイト文字を構成する場合、char-ready?
を返した
ポートから文字全てを読み込もうとすると、ブロックする可能性があります。
(通常の使用状況ではそのようなことは起きないでしょうが、理論的には起こり得ます。
慎重を期したい場合はread-uvector
でバイトシーケンスとして読み込んだ後、
入力文字列ポート等を使って文字毎に読むようにして下さい。)
[R7RS base]
portから1バイトをすぐに読み込める状態なら#t
を返します。
これは伝統的にbyte-ready?
と呼ばれてきましたが、
R7RSではu8-ready?
と名づけています。どちらも使えます。
Next: 読み込み時コンストラクタ, Previous: データの読み込み, Up: 入力 [Contents][Index]
現在のリーダ字句モードを表すパラメータです。 このパラメータの値を変えることにより、旧来のGaucheとR7RSで非互換となる コーナーケースの字句構文についての解釈を変更できます。
一般的には、このパラメータを直接変える必要のある場合は少ないでしょう。 字句構文は読み込み時に問題となりますが、パラメータの変更は実行時に起こります。 いつ、何が起きるかを正確に把握しているのでない限り、意図したとおりの効果は 得られないかもしれません。
hash-bang指示子 #!gauche-legacy
および #!r7rs
は
間接的にこのパラメータに影響を与えます。#!gauche-legacy
は
リーダモードをlegacy
に、#!r7rs
はstrict-r7
に
変更します。
コマンドライン引数の-fwarn-legacy
はデフォルトのリーダモードを
warn-legacy
にセットします。
load
中にこのパラメータを変更した場合、その効果はload
の期間中に
留まります。そのload
が終了した時点で、
load
開始時のリーダモードが復元されます。
このパラメータは値として次のシンボルのうちいずれかを取ります。
permissive
これがデフォルトのモードです。二つの構文の、ほどほどの妥協点を見つけようとします。
文字列リテラル中の16進数エスケープシーケンスは、まず
R7RSの字句構文で解釈されます。もしそれがR7RSの16進数エスケープシーケンスの
構文に沿っていなかった場合は、旧来のGaucheの構文で解釈されます。
例えば、"\x30;a"
は、セミコロンがR7RSのエスケープシーケンスの終端と
みなされ、"0a"
と読まれます。一方で "\x30a"
も "0a"
と
読まれます。終端のセミコロンが無く、有効なR7RSの構文でないため、
旧来のGaucheのエスケープシーケンスとして2桁固定で解釈されるからです。
このモードでは、新たなコードでR7RSエスケープシーケンスを利用できるうえ、
ほとんどの既存のGaucheコードはそのまま走ります。
しかし、もし既存のGaucheコードで、たまたま2桁の16進数エスケープの直後に
セミコロンがあった場合、それはR7RSとして読まれ、非互換性が生じます。
strict-r7
厳密にR7RS互換なモード。
リーダが、hash-bang指示子#!r7rs
に出会うと、ファイルの残りの部分は
このモードで読まれます。
このモードでは、Gaucheの拡張字句構文はエラーとなります。
他のR7RS実装でも読めるコードであることを確認したい場合に使ってください。
legacy
リーダは以前の(0.9.3.3及びそれ以前の)Gaucheと同じように動作します。
リーダがhash-bang指示子#!gauche-legacy
に出会うと、ファイルの残りの部分は
このモードで読まれます。
このモードが必要になるのは、2桁の16進数エスケープの直後にセミコロンが来ているリテラルを、
文字+セミコロンと読みたい場合だけです。例えば
"\x30;a"
を"0a"
でなく"0;a"
と読みたい、等。
こういうケースはコードではほとんど無いと思いますが、
データを文字列リテラルとしてダンプしてある場合、それも不完全な文字列として
ダンプしてある場合に、こういった並びが出現しているかもしれません。
warn-legacy
リーダはpermissive
モードと同様に動作しますが、
旧来の16進数エスケープに出会うと警告を発します。
gosh
に-fwarn-legacy
コマンドライン引数が与えられた場合は
これがデフォルトのモードになります。
コードの中に非互換なエスケープシーケンスが無いかどうか調べるのに役立ちます。
Next: 入力ユーティリティ手続き, Previous: リーダー字句モード, Up: 入力 [Contents][Index]
SRFI-10で定義されている読み込み時コンストラクタは、ユーザ定義の構造の 外部表現を作るための簡単な方法を提供します。
#,(tag arg …)
[SRFI-10] Gaucheはtag (シンボル)をコンストラクタ手続きに関連付ける グローバルなテーブルを管理しています。
リーダーがこの構文に出会ったとき、arg …を読み込み、 tagに関連付けられた読み込みコンストラクタを探し、 arg …を引数としてそのコンストラクタを呼び出し、 その構文を読み込んだ結果としてそのコンストラクタが返した値を挿入します。
この構文はリーダー内部で処理されることに注意して下さい。評価器は argを見ず、リーダーが返したオブジェクトしか見ません。
[SRFI-10] 読み込みコンストラクタprocedureをtagに関連付けます。
例:
(define-reader-ctor 'pi (lambda () (* (atan 1) 4))) #,(pi) ⇒ 3.141592653589793 '(#,(pi)) ⇒ (3.141592653589793) (define-reader-ctor 'hash (lambda (type . pairs) (let ((tab (make-hash-table type))) (for-each (lambda (pair) (hash-table-put! tab (car pair) (cdr pair))) pairs) tab))) (define table #,(hash eq? (foo . bar) (duh . dah) (bum . bom))) table ⇒ #<hash-table eq? 0x80f9398> (hash-table-get table 'duh) ⇒ dah
write-object
メソッド(出力参照)と組み合わせて、
読み戻ることが可能なフォームで書かれたユーザ定義のクラスを
作ることが簡単になります。
(define-class <point> () ((x :init-value 0 :init-keyword :x) (y :init-value 0 :init-keyword :y))) (define-method write-object ((p <point>) out) (format out "#,(<point> ~s ~s)" (ref p 'x) (ref p 'y))) (define-reader-ctor '<point> (lambda (x y) (make <point> :x x :y y)))
注意: define-reader-ctor
の効果の範囲はSRFI-10には
規定されておらず、SRFI-10をサポートする実装においても互換性の問題を
起こすことがあるかもしれません。
(実際に、define-reader-ctor
の存在そのものが実装の選択に
任されています。)
Gaucheでは、現時点においては、define-reader-ctor
はそのフォームが
コンパイルされ評価された時点で効力を持ちます。
Gaucheはトップレベルのフォームを順番にコンパイル・評価するので、
define-reader-ctor
で指定されたtagは、その指定の直後から
使えます。
しかし、define-reader-ctor
の呼び出しとtagの使用が
begin
フォームで囲まれている場合は、begin
フォーム全体は
評価される前に一度にコンパイルされるため、うまく動作しません。
他の実装では、define-reader-ctor
の呼び出しが効力を持つようにする
前にファイル全体を読み込むことを要求するかも知れません。
その場合は、define-reader-ctor
と定義されたtagの使用を
同じファイルに置く事は実質的に不可能です。
可能ならば、define-reader-ctor
の呼び出しと、tagの使用は
異なるファイルに分離されることが望まれます。
現在のdefine-reader-ctor
に関するもう1つの問題は、それが
Gaucheシステムのグローバルテーブルを変更してしまうことで、それゆえに
モジュール性が良くありません。
複数人によって書かれたコードは同じタグを使っているかも知れず、
期待されない結果を引き起こすかも知れません。
作者にはまだ明確なアイデアがありませんが、将来のバージョンでは、
Gaucheにはtagのスコープをカプセル化する方法が導入されるかも
しれません。
Previous: 読み込み時コンストラクタ, Up: 入力 [Contents][Index]
便利な入力手続きです。APIはScshとSTkから取りました。
port->string
はportをEOFまで読み込み、
読んだものを文字列として返します。
port->list
は手続きreaderをportに繰り返し適用し、
結果をリストに蓄積します。readerがEOFを返したら
蓄積されたリストを返します。
port自体はクローズされないことに注意してください。
port->string-list
はport->list
を
read-line
で特定化したもので、
port->sexp-list
はport->list
を
read
で特定化したものです。
もし入力にGaucheの内部エンコーディングにおいて有効な文字をつくらない
バイト列が含まれていた場合、port->string
とport->string-list
の返り値には不完全な文字列が含まれている可能性があります。
入力をバイナリデータとして扱いたい場合は、
gauche.uvector
のport->uvector
を使って下さい
(ユニフォームベクタのブロック入出力参照)。
readerによって読まれる入力に対する便利な繰り返し手続きです。
実際にはこれらの手続きはポートからの入力以外にも使えるため、それぞれ
generator-fold
, generator-fold-right
,
generator-for-each
, generator-map
によって置き換えられました。
詳しくは生成された値の畳み込みを参照してください。
これらの手続きは後方互換性のためだけに提供されています。
• 出力ルーチンの階層: | ||
• 出力制御: | ||
• オブジェクトの出力: | ||
• フォーマット出力: | ||
• 低レベル出力: |
Gaucheにはたくさんの出力手続きがあり、慣れないうちはどれを使えばよいか迷ってしまう かもしれません。次の表は様々な出力手続きを分類するものです。
Schemeオブジェクトを出力する手続きです。 もっと低レベルの手続きもありますが、一般にはこの層が出力ルーチンの基本であると みなされています。Schemeのオブジェクト単位で操作をするからです。 この手続き群はさらに次の二つのグループに分かれます。
write
, write-shared
, write-simple
—これらは、Schemeオブジェクトの外部表現を出力します。出力されたものは一般に、
read
を使って、可能な限り情報を失わずに読み戻すことができます1。
Schemeオブジェクトの外部表現は、リテラルデータをプログラム中に記述するのと
同じ書き方なので、これがSchemeオブジェクトを出力する際のデフォルトの表現になっています。
display
, print
, newline
.
これらは人間に読みやすいプレーンテキストな出力を生成します。
出力を指定の桁数に揃えたり、どちらかに寄せたり、といった書式を指定できる手続きが
format
です。Cのprintf
のようなものです。
「生」のデータを扱う手続きです。
write-char
, write-byte
write-string
,
write-uvector
flush
, flush-all-ports
Lisp構造の出力形式は、
<write-controls>
オブジェクトによって制御することができます。
Lispオブジェクトを出力するルーチン(write, display
等)や、
高レベル出力手続き(format
等)は出力制御オブジェクトを取ることができます。
以下の例で、出力制御の様子がわかると思います。
(write '(1 10 100 1000) (make-write-controls :base 16 :radix #t)) prints (#x1 #xa #x64 #x3e8) (write (iota 100) (make-write-controls :length 5)) prints (0 1 2 3 4 ...)
make-write-controls
手続きは出力制御オブジェクトを作って返します。
出力制御オブジェクトは以下のスロットを持ちます
(これらのスロット名はCommon Lispの対応するプリント制御変数から取られました)。
このスロットが非負整数の場合、それはリストとベクタ(ユニフォームベクタを含む)の
要素が表示される最大数を指定します。もしリストやベクタがその最大数より
多くの要素を持っている場合、残りの要素は表示されず、代わりに
...
が表示されます。このスロットが#f
(デフォルト)であれば、
すべての要素が表示されます。
このスロットが非負整数の場合、それは構造体(リストとベクタ)が表示される
最大の深さを指定します。もし構造体がより深いノードを持っている場合、
そのノードの代わりに#
が表示されます。
このスロットが#f
(デフォルト)であれば、すべての要素が表示されます。
このスロットは2以上36以下の整数でなければならず、 正確な整数を印字する基数を指定します。デフォルトは10です。
このスロットは真偽値を持ちます。真であれば、
正確な整数の前に常に基数プレフィクスが表示されます。
デフォルトは#f
です。
このスロットが真の値の場合、 指定されたカラム幅内で、改行やインデントを適宜挿入してネストした構造を見やすく表示する、 プリティプリント機能を使います。
このスロットが非負整数の場合、プリティプリントで使われる表示幅を指定します。
出力制御オブジェクトは変更不可です。もし既に存在する制御オブジェクトを
少し変えたものが欲しければ、write-controls-copy
が使えます。
註: 0.9.5で<write-controls>
オブジェクトを導入した際には、
Common Lispのスペシャル変数の名前をそのまま反映した
print-length
、print-pretty
等をスロット名としていました。
しかし出力制御オブジェクトの一部であることがわかっている以上、print-
の部分は冗長です。
そこで0.9.6からスロット名を変えました。
互換性のために、make-write-controls
とwrite-controls-copy
は
古いスロット名でのキーワード引数も認識します。
スロットを直接指定してアクセスしているコードは修正の必要があります。
1.0リリース時に古いスロット名のサポートを落とす予定です。
出力制御オブジェクトを作って返します。
出力制御オブジェクトcontrolsのコピーを返します。もしキーワード引数が 与えられれば、その要素が置き換えられます。
高レベル出力手続きはwrite-object
メソッドを経由して再帰的に
呼ばれる可能性があります。その場合、大元の出力手続き呼び出しに渡された
出力制御オブジェクトが、同じポートに対して出力する再帰的に呼ばれる
出力手続きへと自動的に引き継がれます。
以下の手続きで、省略可能な引数portは出力ポートでなければなりません。 省略された場合が現在の出力ポートが使われます。
いくつかの手続きはport/controls引数を取ります。これは、出力ポート
もしくは<write-controls>
オブジェクトでなければなりません。例えば
write
は二つまでそういった引数を取ります。すなわち、write
は
以下のいずれの形式でも呼び出すことができます:
(write obj)
、(write obj port)
、
(write obj controls)
、(write obj port controls)
、
(write obj controls port)
。
省略された場合、ポートは現在の出力ポート、出力制御についてはデフォルトの出力制御が
使われます。
[R7RS+ write]
write
族の手続きは、Schemeオブジェクトの外部表現を出力します。
外部表現は一般に、read
手続きによって読み戻せば、
元のオブジェクトと同等のオブジェクトとなります。
この3つの手続きの違いは、共有構造および循環構造の扱いにあります。
write
は循環構造に対して安全です。データに循環がある場合は
データラベル記法(#n=
と#n#
)を用いてそれを表現します。
循環の無い、単に共有されているだけの構造についてはデータラベル記法を使いません
(二番目の例参照)。
(let1 x (list 1) (set-cdr! x x) ; create a cycle (write x)) ⇒ shows #0=(1 . #0#) (let1 x (list 1) (write (list x x))) ⇒ shows ((1) (1))
write-shared
も循環構造に対して安全で、さらに共有構造も
データラベル記法で表示します。グラフ構造のトポロジーを維持する必要がある
場合はこの手続きを使ってください。
(let1 x (list 1)
(write-shared (list x x)))
⇒ shows (#0=(1) #0#)
最後に、write-simple
はオブジェクトを、共有構造や循環構造を考慮せずに
再帰的に表示します。これは出力前に共有構造の検出パスを走らせる必要がないので
高速です。しかし、渡されたデータに循環があった場合は出力が止まらなくなります。
これらの手続きが表示中にユーザ定義クラスのオブジェクトに
出会った場合は、ジェネリックファンクションwrite-object
を呼び出します。
歴史的経緯: write
は以前のScheme標準にもありましたが、
循環構造があった場合の振る舞いはR7RSになるまで規定されていませんでした。
実際、Gauche 0.9.4より前のwrite
は、循環構造を渡されると停止しません。
SRFI-38はデータラベル記法および、それを表示するための
write-with-shared-structure
とwrite/ss
手続きを導入し、
Gaucheもそれをサポートしていました。
R7RSでこの問題は明確にされ、Gaucheも0.9.4からそれに沿っています。
[SRFI-38]
これらは上記のwrite-shared
の別名です。
Gaucheは、STklosから取ったwrite*
という名前を長く使ってきましたが、
srfi-38によってwrite-with-shared-structure
とwrite/ss
が
定義されました。これらの名前は互換性のためだけに残してあります。
新たなコードにはwrite-shared
を使ってください。
[R7RS write] オブジェクトobjの人に読みやすい表現を、出力ポートに書き出します。
objが循環構造を含んでいる場合は、データラベル記法を使って出力します。
display
が表示中にユーザ定義クラスのオブジェクトに
出会った場合は、ジェネリックファンクションwrite-object
を呼び出します。
(display "\"Mahalo\", he said.") ⇒ shows "Mahalo", he said. (let ((x (list "imua"))) (set-cdr! x x) (display x)) ⇒ shows #0=(imua . #0#)
expr … をdisplay
を使って現在の出力ポートに表示し、
最後に改行を書き出します。
objをportに整形出力(prettyprint)します。
portが省略された場合は現在の出力ポートが使われます。
この手続きと同じ効果は、write
手続きにpretty
スロットを#t
にした
出力制御オブジェクトを渡すことでも得られます
(実際、内部的にはpprint
はそうやってwrite
を呼び出しているだけです)
が、手軽に整形出力を試してみたい場合にこの手続きは便利です。
デフォルトでは、pprint
はobjを出力した後に改行します。
この動作を抑制するにはnewlineに#f
を渡してください。
出力の整形をカスタマイズするには、controlsキーワード引数に出力制御オブジェクトを
渡します(渡された制御オブジェクトのpretty
スロットは無視されます;
常に出力は整形されます)。さらに、出力制御オブジェクトの
width
、length
、level
スロットを、
それぞれのキーワード引数でオーバライドできます。
controlsを省略した場合は適切なデフォルト値が使われます。
出力制御の詳細については出力制御を参照してください。
(pprint (make-list 6 '(gauche droite))) ⇒ prints ((gauche droite) (gauche droite) (gauche droite) (gauche droite) (gauche droite) (gauche droite)) (pprint (make-list 6 '(gauche droite)) :width 20) ⇒ prints ((gauche droite) (gauche droite) (gauche droite) (gauche droite) (gauche droite) (gauche droite)) (pprint (make-list 6 '(gauche droite)) :length 3) ⇒ prints ((gauche droite) (gauche droite) (gauche droite) ....) (pprint (make-list 6 '(gauche droite)) :level 1) ⇒ prints (# # # # # #)
このメソッドをつかって、オブジェクトをどのように印字するかをカスタマイズ できます。
[R7RS base]
portに改行文字を書き出します。(write-char #\newline port)
や
(display "\n" port)
と書いても同じことですが、歴史的な理由から
残されています。
[SRFI-28+]
string の指示に従い、arg …をフォーマットします。
この手続きはCommonLispのformat
のサブセットに、Gauche独自の拡張を
加えたものです。また、これはSRFI-28 "Basic format strings" のスーパーセットに
なっています (https://srfi.schemers.org/srfi-28/srfi-28.html)。
dest引数は出力先を指定します。それが出力ポートであれば、フォーマットされた
結果はそのポートに書き出されます。portが#t
であれば、結果は
現在の出力ポートに書き出されます。portが#f
であれば、結果は
文字列としてformat
から返されます。
destは省略することもできます。その場合は、dest
に#f
を指定したのと同じ動作をします(SRFI-28のformat
)。
controls引数は<write-controls>
オブジェクトで、
~s
と~a
の出力に影響を与えます
(出力制御参照)。これはGauche独自の拡張です。
(format
の関数シグネチャはちょっと変わっていますが、
これは便利さを優先したためです。portとcontrolsはどちらも省略可能で、
またどの順序でも指定できます。)
stringはフォーマット指示子を含んだ文字列です。
フォーマット指示子はチルダ‘~
’から始まり、特定の文字で終了する文字の並びで、
それぞれのフォーマット指示子が対応するargを取りフォーマットします。
string内のフォーマット指示子以外の文字列はそのまま出力されます。
(format #f "the answer is ~s" 42) ⇒ "the answer is 42"
フォーマット指示子は一つ以上のコンマで区切られたパラメータを取ることもできます。 パラメータは整数か文字です。文字の場合、クオート文字に続けてその文字を置きます。 パラメータが省略された場合は既定値が使われます。パラメータの意味はフォーマット指示子毎に 異なります。
さらに、フォーマット指示子は2種類のフラグ、‘@
’ と ‘:
’ を
取ることができます。これらの組合せでフォーマットの動作が変わります。フラグは
(もしあれば)パラメータの後、指示子の文字の直前に置かれなければなりません。
パラメータの位置に文字 ‘v
’ か ‘V
’ を置くこともできます。
その場合、パラメータの値が引数リストから取られます。対応する引数は整数か
文字、または#f
でなければなりません。#f
の場合はそのパラメータが
省略されたのと同じになります。
いくつかの例です。
~10,2s
パラメータ10と2を伴う、フォーマット指示子~s
。
~12,,,'*A
第1パラメータに数値12、第4パラメータに文字‘*
’を取るフォーマット指示子~a
。
第2と第3のパラメータは省略されています。
~10@d
フォーマット指示子~d
。パラメータ10と‘@
’フラグがついています。
~v,vx
フォーマット指示子~x
。第1パラメータと第2パラメータは引数リストから取られます。
以下にサポートされているフォーマット指示子を示します。フォーマット指示子の文字自体は 大文字であっても小文字であっても構いません。特に断りのない限り両者は同じ動作をします。
パラメータ: mincol,colinc,minpad,padchar,maxcol
ASCII出力。対応する引数がdisplay
を使ってフォーマットされます。
整数がmincolに与えられた場合、それは出力される最小の文字数を指定します。
引数のフォーマット結果がmincolより短ければ、空白が右に追加されます(つまり、
左詰めになります)。
colinc、minpad、そしてpadcharは更に細かいパディング方法を 指定します。padcharに文字が与えられた場合、それが空白文字の代わりにパディング文字と して使われます。minpadに0以上の整数が与えられた場合、少なくともその数だけの パディング文字が追加されます。colincが指定された場合、 追加されるパディング文字の数がcolincの倍数に調整されます。
アトマーク ‘@
’ フラグが与えられた場合、結果は右詰めになります。
maxcolパラメータは与えられていれば書かれる文字数の上限を指定します。
フォーマット後の文字列の長さがmaxcolを超えた場合、maxcol文字だけが
書かれます。コロン ‘:
’ フラグが同時に与えられていれば、
maxcol - 4 文字が書かれた後、文字列“ ...
”が書かれます。
(format #f "|~a|" "oops") ⇒ "|oops|" (format #f "|~10a|" "oops") ⇒ "|oops |" (format #f "|~10@a|" "oops") ⇒ "| oops|" (format #f "|~10,,,'*@a|" "oops") ⇒ "|******oops|" (format #f "|~10,,,'☆a|" "oops") ⇒ "|oops☆☆☆☆☆☆|" (format #f "|~,,,,10a|" '(abc def ghi jkl)) ⇒ "|(abc def gh|" (format #f "|~,,,,10:a|" '(abc def ghi jkl)) ⇒ "|(abc de ...|"
パラメータ: mincol,colinc,minpad,padchar,maxcol
S式出力。対応する引数がwrite
を使ってフォーマットされます。
パラメータの意味は~A
指示子と同じです。
(format #f "|~s|" "oops") ⇒ "|\"oops\"|" (format #f "|~10s|" "oops") ⇒ "|\"oops\" |" (format #f "|~10@s|" "oops") ⇒ "| \"oops\"|" (format #f "|~10,,,'*@s|" "oops") ⇒ "|****\"oops\"|" (format #f "|~10,,,'★s|" "oops") ⇒ "|\"oops\"★★★★|"
パラメータ: なし
文字出力。対応する引数は文字でなければなりません(そうでなければエラーが報告されます)。
フラグが無ければ文字はdisplay
で表示されます。
アトマークフラグが与えられた場合はwrite
で表示されます。
パラメータ: mincol,padchar,commachar,interval
10進出力。対応する引数が10進数表記でフォーマットされます。もし引数が数値でなければ、
全てのパラメータは(‘v
’パラメータの処理後に)無視され、
引数は~A
でフォーマットされます。
もしmincolに整数が与えられたら、それが最小の文字数を指定します。 結果の文字数がそれより少なければ、文字padcharが左に追加されます(右詰めになります)。 padcharが省略された場合は空白文字が使われます。
(format #f "|~d|" 12345) ⇒ "|12345|" (format #f "|~10d|" 12345) ⇒ "| 12345|" (format #f "|~10,'0d|" 12345) ⇒ "|0000012345|"
アトマーク ‘@
’ フラグが与えられた場合、正の引数に対して ‘+
’ が
先頭につけられます。
コロンフラグ ‘:
’ が与えられた場合、結果の文字はinterval文字毎に
まとめられ、間に文字commacharが挿入されます。デフォルトでは3文字毎にコンマが
挿入されます。
(format #f "|~:d|" 12345) ⇒ "|12,345|" (format #f "|~,,'_,4:d|" -12345678) ⇒ "|-1234_5678|"
パラメータ: mincol,padchar,commachar,interval
2進出力。対応する引数が2進数の整数としてフォーマットされます。
パラメータの意味は~D
と同じです。
パラメータ: mincol,padchar,commachar,interval
8進出力。対応する引数が8進数の整数としてフォーマットされます。
パラメータの意味は~D
と同じです。
パラメータ: mincol,padchar,commachar,interval
16進出力。対応する引数が16進数の整数としてフォーマットされます。
フォーマット指示文字に ‘X
’ が与えられた場合は ‘ABCDEF
’ が桁文字として
使われ、 ‘x
’ が与えられた場合は ‘abcdef
’ が桁文字として使われます。
パラメータの意味は~D
と同じです。
(format #f "~8,'0x" 259847592) ⇒ "0f7cf5a8" (format #f "~8,'0X" 259847592) ⇒ "0F7CF5A8"
パラメータ: width,digis,scale,ovfchar,padchar
浮動小数点数出力。
引数が実数ならば、その十進小数表現が出力されます。
widthパラメータは出力幅を指定します。もしフォーマット結果がその幅に足りなければ、
左側にpadcharが詰められます。padcharのデフォルトは#\space
です。
フォーマット結果がwidthを越える場合、ovfcharが指定されていれば、
width個のovfcharが出力され、指定されていなければwidthを無視して
必要なだけの桁数が出力されます。
(format "~6f" 3.14) ⇒ " 3.14" (format "~6f" 3.141592) ⇒ "3.141592" (format "~6,,,'#f" 3.141592) ⇒ "######" (format "~6,,,,'*f" 3.14) ⇒ "**3.14"
digitsパラメータは小数点以下に表示される桁数を指定します。非負整数でなければなりません。
省略された場合は、引数の浮動小数点数を一意に同定できる最小の精度で出力されます
(write
やdisplay
で出力した場合と同じです。その文字列をread
すれば、
全く同じ浮動小数点数が得られます)。
(format "~6,3f" 3.141592) ⇒ " 3.142" (format "~6,0f" 3.141592) ⇒ " 3." (format "~10,4f" 355/113) ⇒ " 3.1416" (format "~10,4f" 3) ⇒ " 3.0000"
scaleパラメータが与えられた場合、引数が表示前に(expt 10 scale)
倍
されます。
@
フラグが与えられると、引数が非負の場合にプラス記号が出力されます。
(format "~8,3@f" 3.141592) ⇒ " +3.142"
digitsが浮動小数点数を一意に同定するための桁数に足りない場合は、 小数点以下digits桁に丸められます。デフォルトでは、この丸めは 浮動小数点数の真の値に基づいて行われます。つまり、真の値に近い方の表現が選ばれます。 これはたまに、非直感的な結果になることがあります。 例えば1.15を1/100の位で丸めて(1/10単位にして)みると、 小学校でやる算数と異なり、1.1が出力されます。 この理由は、1.15と表記される浮動小数点数が実際には1.15よりわずかに小さく、従って 1.1により近いからです。本当は1.15でないものを1.15と表示しているのは、 1.15により近い浮動小数点数が他に無く、1.15が該当する浮動小数点数を一意に同定する 最も短い表現だからです。
しかし日常的なアプリケーションでこういう振る舞いがあると、ユーザが混乱してしまうかもしれません。
そこでもう一つ、表示に基づいた丸め(notational rounding)モードを用意しました。
このモードでは、浮動小数点数の表示に基づいた丸めを行います。従って
1.15を1/100の位で丸めると1.2になります。このモードは:
フラグで指定できます。
(format "~6,1f" 1.15) ⇒ " 1.1" (format "~6,1:f" 1.15) ⇒ " 1.2"
パラメータ: digits,idigits,width,padchar
通貨の表示に適した浮動小数点数フォーマット。
digitsは小数点以下に表示される桁数で、デフォルトは2です。
idigitsは小数点より前に表示される最小の桁数で、デフォルトは1です。
widthパラメータは全体の最少文字数を指定し、表示すべき文字がそれより少ない場合は
padcharが左側に足されます。widthのデフォルトは0、
padcharのデフォルトは#\space
です。
@
フラグが与えられた場合、非負の値に+
符号が表示されます。
:
フラグが与えられた場合、符号があればそれはパディングより前に表示されます。
引数が実数でない場合は、あたかも~wD
(w
はwidth) が指定
されたかのように表示されます。
Gauche特有の機能として、数値は表示に基づいた丸め(notational rounding)で
丸められます。表示に基づいた丸めについては上の~F
の項の説明を参照してください。
(map (cut format "~$" <>) '(1.23 4.5 6)) ⇒ '("1.23" "4.50" "6.00")
再帰的フォーマッティング。この指示子はまず文字列を引数に取り、それをフォーマット文字列と 解釈します。そしてさらに次の引数(それはリストでなければなりません)を、そのフォーマット文字列 への引数と解釈します。
(format "~s~?~s" '< "~s ~s" '(a b) '>) ⇒ "<a b>"
@
フラグが与えられた場合は、最初の引数をフォーマット文字列と解釈した後で、
続く引数を必要なだけそのフォーマット文字列の引数として使います。
(format "~s~@?~s" '< "~s ~s" 'a 'b '>) ⇒ "<a b>"
パラメータ: count
引数のカウンタをcountだけ後方にずらします。つまり、count個の引数が
無視されることになります。countのデフォルト値は1です。
コロンフラグが与えられた場合は引数カウンタを前方に動かします。
例えば~:*
は次のディレクティブが直前に使った引数を再び使うようにします。
アトマークフラグが与えられた場合は、countが引数の絶対位置を示します。
0が最初の引数です。
パラメータ: count
それぞれ、文字#\~
、#\newline
、#\tab
、#\page
を
ひとつ出力します。countが与えられた場合はその個数出力します。
[R7RS base] 文字charをポートに出力します。
[R7RS base] 出力ポートに1バイトのデータbyteを書き出します。 byteは0から255の間の正確な整数でなければなりません。
これは伝統的にwrite-byte
と呼ばれてきましたが、
R7RSではwrite-u8
と名づけています。どちらも使えます。
[R7RS base]
startとend引数が省略された場合は(display string oport)
と
同じです。それらが与えられた場合は、string中の指定範囲のみが出力されます。
それぞれ、port、および全てのポートにバッファされているデータを 全て書き出します。
手続き"flush"はScheme実装によって様々な名前で呼ばれています:
force-output
(Scsh, SCM)、
flush-output
(Gambit)、flush-output-port
(Bigloo) 等。
flush
の名前はSTkとSTklosから取りました。
R7RSではflush-output-port
を採用しています。
ある意味、
これは他のプログラミング言語でシリアライズやマーシャライズと呼ばれている操作に
似ています。一般のSchemeオブジェクトをディスクやネットワークにwrite
して、
それをread
すれば、元のオブジェクトと等価なオブジェクトが得られるわけです。
Lisp族言語では、これはread/write invarianceと呼ばれ、組み込みの機能と
なっています。但しオブジェクトによってはこの性質を持たないものもあり、
そういうオブジェクトを扱う時は専用のシリアライザを書く必要があります。