🔥

SchemeのMonadic Formattingとは

2020/12/06に公開

Lisp Advent Calendar 2020 5日目の記事です。
別のことをかくつもりでしたが、翌日になってしまったのです。そういうわけで、ぱっと良さげなSRFIのライブラリを1つ紹介することにしました。

Monadic Formattingとは

Monadic Formatting(SRFI 166)は、printfのようないくつかのSchemeオブジェクトを整形して、文字列を得る、あるいは出力するような用途で使われます。
文字列のテンプレートを使うものではなく、出力対象をformatter単位で分割して、それを組み合わせるというものです。(文字列テンプレートのFormattingはSRFI48にあります)
Parser Combinatorというものがありますが、あれの出力版と思えばよいと思います。
利点は複雑な出力を簡潔に書けるということです。
この記事を書いた時点では、Monadic Formattingの旧バージョンであるCombinator Formatting(SRFI 159がChibi-schemeで使うことができるので、それで動かしました。
Gaucheにはたぶん次のリリースで、Combinator Formattingという形かもしれませんが、0.9.10で入るようです。(Gauche:R7RS-large)
R7RS largeには旧バージョンのCombinator Formattingがtangerine Editionで入りました。

Hello World

いくつかのformatterや文字列を結合して出力するくれるのがshow手続きです。
第一引数に#tを与えれば、現在設定している出力ポートに出力し、#fを与えれば文字列を返してくれます。
第二引数以降は0個以上のformatterか文字列、文字を与えます。
2つ目の例のwrittenは、writeした結果のような形式で与えたオブジェクトを出力してくれるようなformatterです。

(show #f "Hello" "World" #\!);"HelloWorld!"
(show #f "Hello" (written (list 1 2 3)));"Hello(1 2 3)"

適切な位置に空白をいれる

2列めのものをそろえたいといったときは、左からn文字分を空白で埋めてくれる、space-toを使います。

(show #t "aaaaa" (space-to 9) 1 nl 
         "aaaaaaa" (space-to 9) 2 nl
	 "a" (space-to 9) 3)
;aaaaa    1
;aaaaaaa  2
;a        3

出力したいものの左右にスペースを置きたいときは、padded{,/right/both}を使います。

(show #f (padded 5 "abc"))
;"  abc"
(show #f (padded/both 5 "abc"))
;" abc "

state-var

Formatterの状態を設定したり取得したりするための変数のようなものです。
例えば、ここは小数は2桁までしか出さないようにしたいとか、出力時の列数や行数を知りたいといった時に使います。
これは、Formatter内の変数のようなもので、Monadic Formatting(新バージョン)ではstate-varは、first classでhygenicですが、Combinator Formatting(旧バージョン)ではそうではないです。

state-varの設定

withを使えば、let式風の記述でstate-varの設定ができます。
例として、state-varのひとつである小数の精度を決めるprecisionを設定してみます。
precision=2を設定した部分は、小数点以下2までしか出てません。

 (show #f (with ((precision 2)) 0.1234567)
                " / "
                (written 0.1234567))
;"0.12 / 0.1234567"

state-varの取得

fnを使えば、lambda式風の記述でstate-varの取得ができます。
例として、state-varのひとつである列数であるcolを取得してみます。

(show #f "AAA " (fn (col) (each "col=" col)))
;"AAA col=4"
(show #f "AAAA " (fn (col) (each "col=" col)))
;"AAAA col=5"
(show #f "AAAAA " (fn (col) (each "col=" col)))
;"AAAAA col=6"

ユーザー定義 state-var

"Monadic Formatting"では、make-state-variableを用いて、ユーザーが自由にstate-varの定義ができます。

formatterの組み合わせ

formatterはオブジェクトだけでなくformatterを受け取り、formatterを返すことのできるものがあります。これを組み合わせることで、より複雑なformatterを作ることができます。
(良さげな例が思いつかなかったので、それっぽいのを書いておきます。)

(import (scheme  base) (scheme write) (srfi 159))

(define (alist-formatter alist)
  (joined (lambda (apair)
            (each (displayed (car apair))
                  (space-to 5)
                  "=> "
                  (displayed (cdr apair))
                  nl))
          alist))


(define (my-list-of-alist-formatter alist-list)
  (joined (lambda (alist)
            (each (alist-formatter alist)
                  nl))
            alist-list))

(show #t
       (my-list-of-alist-formatter
        '(((aaaa . 1)
           (bb . 2)
           (c . 3))
          ((12 . 1)
           (34 . 2)
           (5678 . 3)))))
;aaaa => 1
;bb   => 2
;c    => 3
;
;12   => 1
;34   => 2
;5678 => 3

おしまい

他にもいろいろ高機能なものがありますので、SRFI166を読んで、Monadic Formattingを使えるようになりましょう。
ここでは紹介しませんでしたが、色を付けたり(srfi 166 color)、列の制御(srfi 166 columnar)といったサブライブラリもMonadic Formattingに含まれれていますので、これらを使えばよりよいSchemeでの出力ライフを送れると思います。

Discussion