LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

SATySFiの「最短コード」

これは「SATySFi Advent Calendar 2020」の15日目の記事です。
(14日目は bd_gfngfn さん、16日目は abenori さんです。)

プログラミング言語に関するニッチな話題の1つに「その言語の正当なソースコードで最短のものは何か?」というものがあります。この記事ではSATySFiについて「最短コード」を考察します。

前提

SATySFiの「言語の仕様」は明文化されておらず、また現在はSATySFiの処理系の実装は1つしかないため、「実装が仕様」と見なすのが妥当でしょう。従って、ここでは「SATySFIの正当なソースコード」の定義を「SATySFiの現時点での最新リリース版である0.0.5版でコンパイルした場合にエラーが起こらずにPDFが出力されるソースコード」と定めることとし、またソースコードの長さをバイト数で測ることにします。

注意

  • PDF出力モードについて考えます。
  • 非ASCII文字は結局使わないのでバイト数でも文字数でも同じです。
  • 最短コードを構成する際には必須でない改行や空白文字を除去することになりますが、「除去後」のコードは非常に読みづらくなります。本記事では「除去前」と「除去後」のコードの両方を掲載した上で「除去前」のコードを見ながら解説します。「短さ」を考慮する際には常に「除去後」を前提にしていることに注意してください。

標準ライブラリの使用を認める場合

普通にSATySFiを文書作成に利用する場合は“標準ライブラリ”(mathやstdjaなどの、処理系に付随するパッケージ群)の使用が前提となります。そこでまずは、同じ前提で「最短コード」を考えてみましょう。

コンパイルが通るソースコードを書くためには「document型の値」を作り出す必要があります。実は、標準ライブラリの中に「document型の値を簡単に作る」ためのstandaloneというパッケージが存在します。これを利用するのが最も簡単であり、恐らくこれが最短にもなると思われます。

source1.saty
% 標準ライブラリのstandaloneパッケージを読み込む
@require: standalone
% 以下の型をもつstandalone函数が提供される
%   standalone : block-text -> document
standalone '<> % 引数に空のblock-textを渡すとdocumentが得られる

このコードをコンパイルすると、A4サイズの白紙のPDFが得られます。

「除去後」のコードは次の通り1で、33バイトになります。

minimal1.saty(33バイト)
@require:standalone
standalone'<>

標準ライブラリを使用しない場合

今度は、標準ライブラリも一切使わないこととしてSATySFiのプリミティヴのみを使った場合の「最短コード」を考えてみます。

standaloneパッケージの実装を参照してsource1.satyのコードをプリミティヴのみを使って書き直すと以下のようになります。

source2a.saty
let-inline ctx \math m =
  embed-math ctx m
in
page-break A4Paper
  (fun _ -> (|
    text-origin = (80pt, 100pt);
    text-height = 630pt;
  |))
  (fun _ -> (|
    header-origin  = (0pt, 0pt);
    header-content = block-nil;
    footer-origin  = (0pt, 0pt);
    footer-content = block-nil;
  |))
  (read-block
    (get-initial-context 440pt (command \math)
      |> set-dominant-narrow-script Latin
      |> set-dominant-wide-script Kana)
    '<>)

SATySFiのプリミティヴでdocument型の値を作り出す唯一の方法が「page-break函数を適用する」ことです。従って、page-breakの使用は必須であり、あとは「いかにしてpage-breakの引数をsource2a.satyより短く書けるか」が問題の核心になります。

  • page-breakの第1引数の型はpaper型である。paper型のコンストラクタの名前はどれも7バイト以上あるため、A4Paperは「最短」になっている。
  • 第2・第3引数の型はともに「特定のレコード型の値を別の特定のレコード型の値に変換する函数」である。source2a.satyは定数のレコードリテラルを返す函数を渡している。当該の型の値を返すプリミティヴ函数はないので、結局同様の方針をとるしかなく、あとはレコードの中の基本型の値を「短く」するしかなさそう。
    • length値で一番「短い」のは1cmなどの3バイトの即値である。なのでlength値を全て1cmに置き換える。
    • block-boxes値は恐らくblock-nil(9バイト)が「最短」であると思われる。
  • 第4引数の型はblock-boxes型である。source2a.satyでは
    read-block ... '<>
    となっているが、元のblock-textが空なので、この式の値は「空のblock-boxes」、つまりblock-nilと等価になる。先述の通り、block-boxesの「最短」はblock-nilなのでこれに書き換えればよい。
    • \mathコマンドの定義は不要になるため削除する。

書き換えた後のコードは以下の通りで、実際にこのコードもコンパイルが通ります(出力はやはり白紙)。

source2b.saty
page-break A4Paper
  (fun _ -> (|
    text-origin = (1cm, 1cm);
    text-height = 1cm;
  |))
  (fun _ -> (|
    header-origin  = (1cm, 1cm);
    header-content = block-nil;
    footer-origin  = (1cm, 1cm);
    footer-content = block-nil;
  |))
  block-nil

この時点での「除去後」のコードは185バイトです。

minimal2b.saty(185バイト)
page-break A4Paper(fun_->(|text-origin=(1cm,1cm);text-height=1cm|))(fun_->(|header-origin=(1cm,1cm);header-content=block-nil;footer-origin=(1cm,1cm);footer-content=block-nil|))block-nil

さらに細かい「最適化」をしていきます。コード中に1cmblock-nilが複数回現れるので、これを1文字の変数に置き換えます。

source2c.saty
let (x, y) = (1cm, block-nil)
in
page-break A4Paper
  (fun _ -> (|
    text-origin = (x, x);
    text-height = x;
  |))
  (fun _ -> (|
    header-origin  = (x, x);
    header-content = y;
    footer-origin  = (x, x);
    footer-content = y;
  |))
  y
minimal2c.saty(174バイト)
let(x,y)=(1cm,block-nil)in page-break A4Paper(fun_->(|text-origin=(x,x);text-height=x|))(fun_->(|header-origin=(x,x);header-content=y;footer-origin=(x,x);footer-content=y|))y

xyの2変数を定義する際に
let x=1cm let y=block-nil in ...
よりも
let(x,y)=(1cm,block-nil)in ...
の方が短くなることに注意。

このコードで(x, x)が3回現れています。いろいろ試してみましたが、以下のようにするのが「最短」のようです。

source2.saty
let (x, y) = (1cm, block-nil)
let z = (x, x)
in
page-break A4Paper
  (fun _ -> (|
    text-origin = z;
    text-height = x;
  |))
  (fun _ -> (|
    header-origin  = z;
    header-content = y;
    footer-origin  = z;
    footer-content = y;
  |))
  y
minimal2.saty(173バイト)
let(x,y)=(1cm,block-nil)let z=(x,x)in page-break A4Paper(fun_->(|text-origin=z;text-height=x|))(fun_->(|header-origin=z;header-content=y;footer-origin=z;footer-content=y|))y

というわけで、現状の「最短記録」は173バイトとなりました。

まとめ

今日も一日サティスファイ!:smiley:(まとめろ)


  1. 「除去後」のコードにおいては改行文字はLF(1バイト)で表すものとし、またファイルの末尾に改行文字はないものとします。 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0