Hatena::ブログ(Diary)

::Eldesh a b = LEFT a | RIGHT b このページをアンテナに追加 RSSフィード Twitter

2015-07-17

PolyMLのpretty printerをユーザ定義する

Poly/MLというStandardMLの処理系があります。

このPoly/MLの提供するREPLでは、入力した式の値をエコーバックする際に使用するプリンタ(REPLの'P')をSML自身のコード内で指定することができます。


この記事では Poly/ML のREPLのpretty printerを部分的に上書きして、ユーザ定義のデータ型の表示をカスタマイズする方法を紹介します。

Poly/ML(version 5.5.1 または 5.5.2)を使用します。古いバージョン(5.5系以前)から仕様が変更されているようなので注意して下さい。


モチベーション

デフォルトで用意されているプリンタは優秀なので通常困ることはあまりありませんが、

入り組んだユーザ定義型の値の場合、いちいちコンストラクタが表示に含まれると内容が把握しづらくなることがあります。

例えば典型的な定義のjson型の値は、以下のようにいちいちコンストラクタが表示され、読みづらくなっています。

> Json.sample;
val it =
   JArray
    [JBool true, JBool false, JNull, JInteger 31412341234,
     JObject
      [("foo", JString "foos foosss..."),
       ("bar", JString "bars barsssszzzz.."), ("bazz", JArray [...])],
     JString "aaaaaaabbbbbbdddeeeeeee"]: JSON.value
>

これを例えば、通常のjsonとして表示するようなプリンタに差し替えることが出来れば、以下のように

> Json.sample;
val it =
   [
     true,
     false,
     null,
     31412341234,
     {
       "foo":"foos foosss...",
       "bar":"bars barsssszzzz..",
       "bazz":[ "bazzs", null, 141421356 ]
     },
     "aaaaaaabbbbbbdddeeeeeee"
   ]: JSON.value
>

リテラルそのままの見易い表示にすることが出来るはずです。


Poly/MLは、以上のようなREPLのP(プリンタ)を差し替える機能を提供しています。

以降の章では、上で示したユーザ定義jsonプリンタの構築を例として使い方を解説します。


前提

以下のようなディレクトリレイアウトを前提として話を進めます。

$ ls Json/
JsonTy.sml ml_bind.ML

つまりカレントディレクトリで PolyML.make "Json"; とすると structure Json (+ JsonTy)がロードされます。

これから編集していくのは Json/ml_bind.ML (が公開する structure Json) です。*1

JsonTy.smlはjsonの型だけを提供するファイルで、以下のように型定義のみから成ります。

(* JsonTy.sml *)
structure JsonTy =
  struct
    datatype value
      = JObject of (string * value) list
      | JArray of value list
      | JNull
      | JBool of bool
      | JInteger of IntInf.int
      | JReal of real
      | JString of string
  end

始まりのプリンタ

最も簡単なプリンタの定義として以下の定義からはじめます。

(* ml_bind.ML *)
open PolyML JsonTy
structure Json = struct
  fun pretty _ _ _:t = PolyML.PrettyString "<json>"
end
val () = PolyML.addPrettyPrinter JsonTy.pretty;

これを使ってみると:

> PolyML.make "Json";
> JsonTy.JInteger 314;
val it = <json>: JsonTy.t
> JsonTy.JBool true;
val it = <json>: JsonTy.t
>

JsonTy.t型の値は全て <json> という(ひどい)表記になりました。

ここで定義した JsonTy.pretty を有用な表示をするように改善していきます。


プリンタの登録

上のように、定義したプリンタをREPLが使用するようにするには、専用に提供されている関数 PolyML.addPrettyPrinter を使い、型とそれに対応するプリンタの組を登録します。

PolyML.addPrettyPrinter : (int -> 'a -> 'b -> pretty) -> unit

この関数が要求する関数が登録するプリンタで、定義は以下のようになってい(るものとして説明し)ます。

fun printer
	depth         (* 深さ *)
	printArgTypes (* 引数のプリンタ *)
	value         (* 表示する値 *)
= ...
  • 第1引数は表示する深さです。データ構造のどこまで深い階層まで表示するのかを表します。この値が0以下の場合は PrettyString "..." を返してください。
  • 第2引数は扱おうとしているデータ型に型変数があった場合、その型用のプリンタが渡されます。今回は型変数を扱いませんので、この値は単に捨てます。
  • 第3引数は表示しようとしている値です。

プリンタの実装

プリンタは、表示を指定したい型に対応する pretty 型の値を構築して実装します。

datatype PolyML.pretty =
    PrettyBlock of int * bool * context list * pretty list
  | PrettyBreak of int * int
  | PrettyString of string

PrettyString

まず最も簡単なものから。

PrettyString はその場で(単一の行内に)表示するための文字列を表します。 "true" とか "()" などの改行を挟む余地のない要素の表現に使います。

fun pretty value =
  case value
    of Ty.JInteger x => PrettyString (LargeInt.toString x)
     | Ty.JString  s => PrettyString ("\"" ^ s ^ "\"")
     | Ty.JReal    r => PrettyString (Real.toString r)
     | Ty.JNull      => PrettyString "null"
     | Ty.JBool    b => PrettyString (Bool.toString b)
     | _ => raise Fail "undefined"

これを使うと以下のようになります。

> JReal 3.14;
val it = 3.14: T.value
> JString "hoge";
val it = "hoge": T.value
> JInteger 4242;
val it = 4242: T.value

ちゃんと(?)コンストラクタが表示されなくなりました。

PrettyBlock

PrettyBlock は他のプリンタの集合を一つのまとまったプリンタにするコンストラクタです。

PrettyBlock of int * bool * context list * pretty list
  • 第1引数はこのブロックの表示全体のインデント増加数を指定します。
  • 第2引数は改行の一貫性指定です。true を指定すると、このプリンタの扱う要素のいずれかの表示が1行に収まらない場合、すべての「改行するかも知れない場所」で改行します。
  • context listはユーザが任意に持ち回れる値ですがここでは割愛します。常に空リストで問題ありません。
  • pretty listはそのブロックを構成する子要素のプリンタの集合です。
fun interleave []      _ = []
  | interleave [x]     _ = [x]
  | interleave (x::xs) s = x::s::interleave xs s

fun pretty value =
case value
  of ... => ...
   | ... => ...
   | Ty.ARRAY  xs => 
      P.PrettyBlock (2, true, [],
                          [P.PrettyString "["]
                        @ interleave
                             (map pretty xs)
                             (P.PrettyString ",") (* コンマで区切る *)
                        @ [P.PrettyString "]"])

これを使ってみると:

> JArray [JInteger 314, JString "hoge", JArray [JReal 1.4, JNull]];
val it = [314,"hoge",[1.4,null]]: value

カンマで区切られて大分すっきりした表示になりましたが、スペースが全く無いので多少窮屈ですね。


PrettyBreak

残ったコンストラクタ PrettyBreak は要素間のスペースと改行時のインデント増加数を指定します。

datatype PolyML.pretty =
    ...
  | PrettyBreak of int * int (* スペース, インデント *)
  | ...

これを使って json array の表示を改良します。

fun pretty value =
case value
  of ... => ...
   | ... => ...
   | JArray xs =>
    let
      val pxs = map pretty xs (* Arrayの各要素のプリンタ *)
      fun split xs = foldr (fn (x,[]) => [x]
	                         | (x,xs) => [x, comma, PrettyBreak(1,0)] @ xs)
	                       [] xs
    in
      P.PrettyBlock (indent, consistent, []
			   (* 括弧 '[' + スペース *)
			 , [bracket_open , P.PrettyBreak(1, 0)]
			 @ (split pxs)
			   (* インデント浅く + 括弧 ']' *)
			 @ [P.PrettyBreak(1,~indent), bracket_close])
    end

括弧開く '[' の時点でインデントが入っているので、閉じる ']' 時にのみ符号を反転してマイナスのインデントを指定します。

では実際に大きめのJson Arrayで確認してみます。

> JArray [JInteger 314, JString "hogehogehoge", JArray [JReal 1.4, JArray [JNull, JBool true, JBool false, JNull, JString "foo bar bazz"]]];
val it =
   [
     314,
     "hogehogehoge",
     [ 1.4, [ null, true, false, null, "foo bar bazz" ] ]
   ]: value

要素間のスペース、ブラケットの内側で必要に応じた改行と必要に応じたインデントが追加され、より大きい構造を持つ値も見やすく表示されるようになりました。


まとめ

Poly/MLのREPLを使う際、処理系の提供する関数 addPrettyPrinter を使って JSON型 のプリンタを上書きすることが出来ました。

これは型の定義とは独立して(ml_bind.MLで)定義;設定するため、既存のコードを変更せず導入できます。

もちろん同様の方法で任意のユーザ定義型についてプリンタが設定可能なので、デフォルトの表示で不満がある場合は、適宜プリンタを変更して快適にREPLを使いましょう。


ここで紹介した実装(を元にしたprinter)全体はgitに置いてあります。

*1:PolyML.makeするとml_bind.MLというファイルを探して自動的に読み込んでくれます

2015-07-03

Cygwin/X 1.17に繋げない

新たにCygwinをインストールしたマシン(win7 64bit)上で、Cygwinの提供するXサーバが使えなかった。

より正確には、XWin.exeは起動しているのにxterm等のクライアントが接続できない。

$ xterm
xterm: Xt erorr: Can't open display: 192.168. ...

またこのエラーかよ !!(`Д´)

解決

デフォルトでのXの挙動が変更され、tcp接続を受け付けなくなったのが原因らしい。

以下のように -listen tcp オプションをつけて起動すればよい。

// 変更前のX起動コマンド
$ run xwin -multiwindow
// 変更後X起動コマンド
$ run xwin -multiwindow -listen tcp

$DISPLAY を localhost:0.0 とか指定していると(= tcpを使うようになってると)、ローカルのクライアントも起動できなくなるので注意。


参考

2015-06-25

改訂新版C++ポケットリファレンスレビュー

前回のBoost.勉強会でいただいた 改訂新版C++ポケットリファレンス を一通りさらったのでレビュー(というか紹介)を書いておきます。


どんな本?

この本は、目的から探せる形式でC++の(主に)標準ライブラリを解説した逆引きリファレンスです。

今回私が読んだ第2版(「改訂新版」が付いてる方)では、現在最新の規格であるC++14に対応していて、新たにconstexpr指定が追加された箇所等に解説が加えられています。

各項目には、関連するライブラリのシグネチャと簡単な利用例が完結したコードとして載っていて、もっとも単純な類のユースケースと機能が簡単に把握できます。

コア言語に関連した内容もトピックとしてはだいたい網羅的に紹介されていますので、構文の詳細や特定のライブラリがあることは知っているんだけど、'そら'では出て来ない。といった場合に便利です。


どんな人向け?

古いC++を知っている人、あるいはぼんやりとC++を知っている人が、C++11や14で新たに加えられたライブラリや機能を一通り頭に入れるのに適していると思います。

簡単な使用例が併記されているのも、使い込んでいない機能を手っ取り早く理解するにはポイントが高いところです。

載っているサンプルコード自体も一貫してモダンなスタイルで書かれているので、眺めてみると気付くことがあるかも知れません。


ただし、とっかかりとしてはともかく、この本でC++を勉強しようというのは本書の目的からは外れていると思います。

あくまでリファレンスですので、知っていることを手っ取り早く思い出す記憶の2次キャッシュとして、あるいは目的の機能を実現する手段に辿り着くためのインデックスとしての使い方がオススメです。

本棚ではなく手元に置いておくとよさげ。


その他

ほぼ修正が自明なものばかりではありますが、サンプルコードを中心にいくつか誤植があり、私もいくつか報告しました。

エラッタ著者によるサポートサイト にまとまっているので怪しいと思ったら確認&報告しましょう。


2015-06-01

Boost.勉強会 #17 東京に参加

先日(2015/05/30)行われたBoost.勉強会 #17 東京に久々に参加してきました。

IIJ新社屋がめっちゃ綺麗でビビりつつ滑り込みセーフ(アウト)で会場着。


セッション資料は以下にまとまってます。

Boost.勉強会 #17 東京 タイムテーブル


C++適用ドメインがまったく絞られないので、数値計算(?)からブラウザゲームまで相変わらずバリエーションに富んだ発表内容でした。

印象的だったセッションについて

まず id:redboltz さん の「MessagePackとAPIバージョニング」を聴いてinline namespaceの存在を思い出しました(^^;;

これを知った時はデバッグ版とリリース版を切り替えるというユースケースで紹介されていた気がしますが、バージョニングにも使えそうですね。

ただ名前探索についてかなり詳細に理解している必要がありそうなので、もうすこし設計指針がこなれて来ればいいなぁ…という気がします。


他には @ さんのSiv3Dの発表が見てて楽しかったですね。

processingより型がかなりまともそうなので、次に可視化したいものがあったらSiv3Dを使うのも良さそうです。

あとウィジェットが標準装備なのもかなりポイント高いと思います。


最後に、kou_yeung, paosidufygthrj*1 さんの「C++11やEmscriptenと付き合って1年間の振り返り」。

つまり、emscriptenマジヤバイ。

何度か説明聞いて理屈は分かるんだけど、でも何言ってるのか意味分からない。

その他

個人的な目的でもあったのでDirectShowとMF(MediaFoundation)の話を@さんに聴く。

薄々気付いてはいたものの、教えてもらったことをまとめると、DirectShow is 闇。だれか教えてください。


あと@さんにCOMの勉強方法について教えてもらおうとするも、特に良い資料は無いらしい。つらい。

ATL/COMの本(多分コレ)はとりあえず持っておけとのこと。


f:id:eldesh:20150601220841j:image:w360:right

最後に、じゃんけん大会で勝って、ポケットに入らないと評判のC++ポケットリファレンス第2版(C++14対応)を頂きました!! →

貰ってから知ったんですが本当に出来立てらしく、レビュー書かないといけない流れ(^^;;

個人的にもC++14にはキャッチアップ出来てないことですし、読んでレビュー書くつもりです。

*1:どういう読みなんだろう

2015-05-14

firefoxの更新をキャンセルする


firefoxというブラウザでは [ソフトウェアの更新を確認] すると手元のソフトウェアが最新バージョンに更新される。

中断は(少なくとも素の状態では)出来ない。*1

何を言っているのか分からないと思うが事実だ。

ちなみにこの「ソフトウェアの更新」ボタンは「ヘルプ」メニューの中*2にある。意味が分からない。


ダウンロード自体の中断はどうやるのか分からなかったが、更新ファイルのダウンロードが終わってから以下のフォルダを削除すると更新せずに済んだ(ようだ)。

> C:\Users\<user name>\AppData\Local\Mozilla\updates\<xxxxx>


任意のバージョンが欲しい場合は以下のサイトからダウンロードできる。

> ftp://ftp.mozilla.org/pub/mozilla.org/firefox/releases/

*1アプリケーション自体を終了しても次回の起動時に再開する

*2:孫要素