Gauche:Translation:Devlog:REPLでのプリティプリンタ等

Gauche:Translation:Devlog:REPLでのプリティプリンタ等

(原文: Pretty printer (and more) in REPL )

REPLでのプリティプリンタ等

REPL 上で式を評価し、その結果が巨大なS式であるのを見ると気後れします。 特に Emacs 内で font-lock モードで gosh を実行しているとき、巨大な出力をパースしようとして Emacs は非常に遅くなってしまいます。

私が出力を省略したくない理由は REPL の出力を頻繁にコピーして再利用することにあります。 Emacs ではS式をコピーすることは、それがどんなに大きなものであっても数回のキーストロークに過ぎません。 しかし、それでもなお大きな出力を引き摺りまわしている Emacs はもどかしいです。

Gauche はそれを改善するためのいくつかの仕組みをずいぶん前から持っていましたが、使いやすい機能にまとめました。

サンプルセッション

事例の中で見てわかりやすいちょっとしたツアーに連れていきます。 まず、面白いデータが必要です。

gosh> ,u data.random
gosh> ,u gauche.generator
gosh> (define word (gmap string->symbol (strings-of (integers-poisson$ 12) (chars$ #[A-Z]))))
word
gosh> (define leaf? (samples$ '(#t #f #f #f)))
leaf?
gosh> (define (tree d) (cons (if (or (zero? d) (leaf?)) (word) (tree (- d 1)))
                             (if (or (zero? d) (leaf?)) '() (tree (- d 1)))))
tree

(tree N) は最大深さ N のランダムなネストされたリストを生成します。 何回かやってみて適当な大きさのデータを見つけましょう。

gosh> (tree 5)
((HESUBSPMIBQBBWWZZ (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX (FMEBQP) PSHRSTW) ((UAYIBNNC (XAPYQBPOHSY) QFIZMITEWULRBMEO)) (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ((HZBDNGYBBQD)) YIQZWPL RELGWZEGSR)

良い感じです。 保存しましょう。

gosh> (define t *1)
t

さて、全てが一行にある木は理解しづらいです。 プリティプリントしましょう。

gosh> ,pm pretty #t
Current print mode:
  length :  50
   level :  10
  pretty :  #t
   width :  79
    base :  10
   radix :  #f
gosh> t
((HESUBSPMIBQBBWWZZ (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX (FMEBQP) PSHRSTW)
 ((UAYIBNNC (XAPYQBPOHSY) QFIZMITEWULRBMEO))
 (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ((HZBDNGYBBQD)) YIQZWPL
 RELGWZEGSR)

トップレベルコマンド ,pm,print-mode の省略形です。 はい、プリントモード pretty#t に設定すると、 REPL は結果をプリティプリントします。

プリティプリンタはS式を幅 width に収めようとします。 これは変更可能です。

gosh> ,pm width 40
Current print mode:
  length :  50
   level :  10
  pretty :  #t
   width :  40
    base :  10
   radix :  #f
gosh> t
((HESUBSPMIBQBBWWZZ
  (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX
  (FMEBQP) PSHRSTW)
 ((UAYIBNNC (XAPYQBPOHSY)
   QFIZMITEWULRBMEO))
 (WLQITJTZNBO (GJZNEKWBMLGCWKLPN)
  EINLIRVDLLGPQ)
 ((HZBDNGYBBQD))
 YIQZWPL
 RELGWZEGSR)

まだ長すぎるので、表示されたリストの長さ (length) を制限しましょう。

gosh> ,pm length 3
Current print mode:
  length :   3
   level :  10
  pretty :  #t
   width :  #f
    base :  10
   radix :  #f
gosh> t
((HESUBSPMIBQBBWWZZ
  (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX
  ....)
 ((UAYIBNNC (XAPYQBPOHSY)
   QFIZMITEWULRBMEO))
 (WLQITJTZNBO (GJZNEKWBMLGCWKLPN)
  EINLIRVDLLGPQ)
 ....)

3 要素以上のリストやベクタは省略記号を使って省略されます。 ネストレベルの個数を制限することもできます。

gosh> ,pm level 3
Current print mode:
  length :   3
   level :   3
  pretty :  #t
   width :  40
    base :  10
   radix :  #f
gosh> t
((HESUBSPMIBQBBWWZZ (#) NTAQUDHAXX ....)
 ((UAYIBNNC # QFIZMITEWULRBMEO))
 (WLQITJTZNBO (GJZNEKWBMLGCWKLPN)
  EINLIRVDLLGPQ)
 ....)

現在のレベルより深くネストされたリストは # で示されます。

もし全てが必要な場合、例えばコピペするには、コマンド ,pa toplevel (,print-all の簡略形です) が使えます。 省略なしで前の結果を書き出します。

gosh> ,pa
((HESUBSPMIBQBBWWZZ (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX (FMEBQP) PSHRSTW) ((UAYIBNNC (XAPYQBPOHSY) QFIZMITEWULRBMEO)) (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ((HZBDNGYBBQD)) YIQZWPL RELGWZEGSR)

整数のデフォルト基数を base で変更変更することも出来ます。 radix モードは基数接頭辞 (#b, #x, #nr 等) が表示されるか切り替えます。

gosh> ,pm base 2
Current print mode:
  length :   3
   level :   3
  pretty :  #t
   width :  #f
    base :   2
   radix :  #f
gosh> 4753
1001010010001
gosh> ,pm base 16 radix #t
Current print mode:
  length :   3
   level :   3
  pretty :  #t
   width :  40
    base :  16
   radix :  #t
gosh> 4753
#x1291

さて、デフォルトに戻します。

gosh> ,pm default
Current print mode:
  length :  50
   level :  10
  pretty :  #f
   width :  79
    base :  10
   radix :  #f

デフォルトでは length=50, level=10 で表示されます。 これによって、有用なデータを表示しつつ、誤って大きなS式を表示することを防ぎます。

コンソール出力

Common Lisp は print (Scheme で言う write) の動作に影響する *print-length**print-pretty* といったスペシャル (動的) 変数をいくつか持っています。 我々の REPL の print-mode はそれを真似ますが、個別の動的変数を使うかわりにパッケージされた構造体 <write-controls> があります。 新しい write-controlsmake-write-controls で生成できます。

gosh> (make-write-controls)
#<write-controls (:length #f :level #f :base 10 :radix #f :pretty #f :width #f)>
gosh> (make-write-controls :length 10 :base 2)
#<write-controls (:length 10 :level #f :base 2 :radix #f :pretty #f :width #f)>

表示制御構造体はイミュータブル (不変) です。 もし、既存の制御とわずかに異なる制御が必要なら write-controls-copy を使用して、変更したいキーワード引数を与えることができます。

gosh> (write-controls-copy *1 :pretty #t)
#<write-controls (:length 10 :level #f :base 2 :radix #f :pretty #f :width #f)>

writedisplay といった Gauche の出力手続きはオプショナルな書込み制御を受け入れるように拡張されています。

制限

今のところ、プリティプリンタはリスト、ベクタ、ユニフォームベクタのみを扱います。 他のオブジェクト (カスタムプリンタを持つオブジェクトを含む) は、システムのデフォルトのライタによってフォーマットされているため、ひとかたまりとして改行を挿入せずにレンダリングされます。

プリティプリントは Scheme コードの構文キーワードを認識してインデント調整するといった機能があるべきです。 このような機能は例えばマクロ変換の結果をフォーマットするのに便利です。 いずれそれらをサポートするつもりです。

More ...