サクサク

食パンの広告では「ふんわり」「もっちり」を売り文句にしている場合は多い。 日本でもっちりとしたパンが好まれるのは、これが米のかわりと見做されているからだという説がある。 日本の米は (べっちゃりとしない程度に) 水気を多く含んでやわらかいものが上等とされがちで、その延長線で考えればパンもふんわりしたものが好まれるというのは理解できる。

しかし、私はクッキーの一歩手前くらいのサクサクとした食感が好きだ。 ふんわり食パンを強くトーストしただけではどうにも良いサクサク感にはならない。 サクサク食パンは材料から違うのだろう。 もちろん探せばサクサク食パンも売っているのだが、選択肢が少ないことにはかわりない。

パン以外でもそうだ。 人気があるからどんなものだろうとミスタードーナツが出しているポン・デ・リングを食べてみたらやたらとふわふわで私の好みに合わない。

もっとサクサクしたい。

Document ID: a9a74ad8c073af4a6f452e471bfea23f

こんな夢をみた「エレベータ」

いつも意味不明な夢シリーズだが、今回は毛色の違う夢を見た。

私は高層建築物の中にいた。 滑り台で一階分を下りられるようになっていたので珍しいと思ったのだが、それは下の階へ下りるためのものではなくエレベータホールへと繋がるものであった。 エレベータホールへは階段か滑り台のみで繋がっていた。 つまり、エレベータホールへ行くためには、同時に階段で一階分上がるか滑り台で一階分下がるかしなければならない。 エレベータホールから出るには階段で一階分上がるか階段で一階分下がるかということになる。 (エレベータホールから出るためには滑り台を使えない。) また、エレベータホールとの接続とは別に上下階とは階段で繋がっている。 どの階へ行くにも最低でも一度は階段を上るか下るかする必要があるということだ。

この条件では、ひとつ上の階へ行きたい場合にエレベータを使おうとすると滑り台で一階分下がってエレベータホールに到達し、エレベータで一階分上がってから階段で一階分上がるというのが最短経路になる。 (ここでは階段を上るのと下るのは同じ階数分であれば同じ労力であると仮定している。) それなら階段で一階上った方がマシだということになる。 ひとつ下の階へ行きたい場合も同様である。

二階分下がろうとすると、滑り台でエレベータホールへ行ってからそのまま階段で一階分下りるのが最短になる。 エレベータホールを経由するが、エレベータを使う必要はない。

つまり、この設計だと上一階分の移動と下二階分の移動はエレベータを使わない方が楽なわけで、わずかな移動の場合にはエレベータを使わないように誘導する設計思想なのである。 現実には何かと問題もあるだろうが、夢の中でこの建築物を見た私はとても感心したのだ。

Document ID: 77e457119cc35af24f3e06e793dbc2df

Cの本質

本質というものは状況や立場によって異なる。 例えばどこかの企業で事務を電子化しようとした場合、それをやる技術者にとってはメカニズム (アルゴリズム) が本質だろう。 技術者は技術に対して対価を貰う立場なのだから。 その仕組みを使う事務員はどれだけ作業が効率化できるかが本質だと思うだろう。 経営者にとってはそれの導入がどれだけ収支に影響するかが気にかかるはずだ。

さて、プログラミング言語 C についてしばしば「本質的でない要素が多くて初心者向けではない」という主張を見ることがある。 C は高級アセンブラと揶揄されることもあるように、言語の意味論と機械の意味論が不可分に融合していて、ここで本質的ではないと言われるのは機械の理屈に由来する部分だったりするのだ。 (文法にも型表記などに歴史的負債を引き摺っているわかり難い部分はあるが、ここではそれは脇に置く。)

機械を操ろうというのにその仕組みは本質的ではないのだろうか。 プログラミングを問題解決の道具として見るのならば機械の仕組みは本質的ではないのだろう。 しかし、機械の理屈を理解しようという立場でプログラミング入門するならば、むしろ C こそ本質を捉えているともいえる。

「本質」という言葉を使うときには、それがどのような視点で見たときのものかを意識して擦り合わせをしないと噛み合わない。

Document ID: a0427421a16b8bc485ce026fcc12d1c4

文書構造

いくつかの小説ホスティングサイトから文書を取得して電子書籍 (ePub 形式) を生成するスクリプトを私は以前に作って公開している。

SaitoAtsushi/yomou-publisher at ff3950e

いくつかのサイトに対応していることもあって ePub 生成する部分をひとつのライブラリとしてくくり出しているのだけれど、ひどいインターフェイスでとても汎用的には使えない。 問題のひとつは文章構造の表現のしかただ。

文書と文章構造はリストにまとめて渡すようになっている。 例で言えばおおよそ以下のような形式だ。

("第1章"
 ("001.xhtml" "第1章 第1話 ほにゃらら" "ほにゃらら本文")
 ("002.xhtml" "第1章 第2話 かくかく" "かくかく本文")
 ("003.xhtml" "第1章 第3話 しかじか" "しかじか本文")
 "第2章"
 ("004.xhtml" "第2章 第1話 なんたら" "なんたら本文")
 ("005.xhtml" "第2章 第2話 かんたら" "なんたら本文"))

リストの要素は

  • 章の名前
  • 各話のファイル名と表題と本文をリストでまとめたもの

のいずれかである。

しかしこのような表現では行き詰まりが出てきた。 文章のまとめをネストできないからである。 例えば「第何部」「第何章」「第何節」というようにいくつかの話をまとめてそれを更にまとめるというような構造が表現できないのである。 主要な小説ホスティングサイトではネストする機能がなかったこともあって想定しなかったわけだが、 KADOKAWA が始めたカクヨムというサービスではネストできるようで、実際にそうしている作品もある。 ライブラリとして汎用的にするなら想定して然るべきであった。

単純に考えれば、リストに入れるのだから適当にグループ化すればいいじゃないかと思うだろう。 こんな要領で。

(("001.xhtml" "まえがき" "まえがき本文")
 (section "第1部 導入編"
   ("002.xhtml" "プロローグ" "プロローグ本文")
   (section "第1章"
     ("003.xhtml" "第1章 第1話 最初の話" "ほにゃらら")
     ("004.xhtml" "第1章 第2話 次の話" "ほげほげ")
     ("003.xhtml" "第1章 第3話 一段落" "ふがふが"))
   (section "第2章"
     ("004.xhtml" "第2章 第1話 次の事件" "わはー")
     ("005.xhtml" "第2章 第2話 解決" "むすー")))
 (section "第2部 発展編"
   (section "第3章"
     ("006.xhtml" "第3章 第1話 開始" "かくかく")
     ("007.xhtml" "第3章 第2話 開始" "しかじか")))
 ("008.xhtml" "あとがき" "あとがき本文"))

これならいくらでもネストできる。

とは言っても、そもそも論として文章構造と文章の内容をひとつのリストにまとめるのは妥当だろうか。

各話ごとに書き出してしまえばメモリ上で全体を保持する必要はなく、効率は良くなる。 目次などのメタデータだけは最後に整理して出力すればよい。 これは見掛け上、オブジェクトに対するメッセージとしてインターフェイスを整理できる。

しかし、メッセージは時系列での順序しかない。 ネストをどう表現するかを考えると、グループの開始と終わりを表すメッセージを投げる方法が考えられる。 上の例をメッセージで表すならこうなる。

(let ((book (book-open "ファイル名.epub" "表題" "作者" "概要")))
  (add-html book "001.xhtml" "まえがき" "まえがき本文")
  (start-section "第1部 導入編")
  (add-html book "002.xhtml" "プロローグ" "プロローグ本文")
  (start-section "第1章")
  (add-html "003.xhtml" "第1章 第1話 最初の話" "ほにゃらら")
  (add-html "004.xhtml" "第1章 第2話 次の話" "ほげほげ")
  (add-html "003.xhtml" "第1章 第3話 一段落" "ふがふが")
  (end-section)
  (start-section "第2章")
  (add-html "004.xhtml" "第2章 第1話 次の事件" "わはー")
  (add-html "005.xhtml" "第2章 第2話 解決" "むすー")
  (end-section)
  (end-section)
  (start-section "第2部 発展編")
  (start-section "第3章")
  (add-html "006.xhtml" "第3章 第1話 開始" "かくかく")
  (add-html "007.xhtml" "第3章 第2話 開始" "しかじか")
  (end-section)
  (end-section)
  (add-html "008.xhtml" "あとがき" "あとがき本文")
  (book-close book))

書籍となるとカスタマイズしたい事項は無数に生じる。 縦書き/横書きだとかスタイルシートの指定だとか、そういったものをオプションとして手続きに渡すとなると引数の数が際限なく増えて不格好になるのでそういったオプション要素もオブジェクトに対してメッセージで投げられるようにすれば一貫したインターフェイスに出来るだろう。

この方向でスクリプトを整理しなおすことを考えている。

Document ID: 3067cd99051d86bf097ce5c87f68c459

TL/1 の呼出規約を予想する

cdecl か PASCAL

プログラミング言語 TL/1 について、呼出規約は cdecl か PASCAL を使っているものだろうと先日は予想した。 では cdecl と PASCAL のどちらなのだろうか。 それを検証するためにこのようなプログラムを実際に (TL/1 の実装のひとつである TL/1-FM で) コンパイルして動作させてもらった。

FUNC FOO

BEGIN
  WRITE(0: FOO(1, 2))
END

FOO(X)
BEGIN
  RETURN X
END

呼出規約が素朴な cdecl か PASCAL のどちらかであるという前提の元で、このプログラムが 1 を表示するようなら cdecl で 2 なら PASCAL である。 その理由についてはここでは説明しないが、呼出規約の詳細はウェブ上に情報があるのでそれを参照してもらいたい。

結果は 1 であったとのことで cdecl だろうと予想された。

反例

ところが追加の検証で以下のようなプログラムの実行例をもらった。

PROC ZERO,ONE,TWO

BEGIN
  ZERO(12,23)
  ONE (34,45)
  TWO (56,67)
END

ZERO
VAR X,Y
BEGIN
  WRITE(0:X," ",Y,CRLF)
END

ONE(X)
VAR Y
BEGIN
  WRITE(0:X," ",Y,CRLF)
END

TWO(X,Y)
BEGIN
  WRITE(0:X," ",Y,CRLF)
END

実行結果は以下のようになるのだという。

12 23
34 45
56 67

この情報をくれたはりせん氏はローカル変数と引数渡しの領域がかぶっているのだろうと述べているが、 cdecl ならそんなことはありえない。 TL/1 (の処理系のひとつである TL/1-FM) が cdecl を呼出規約として使っているという予想は間違いだったということだ。

また、このような結果を生みだすようなスタックの利用方法を思い付かないでいる。 実際のコンパイル結果を見れれば手っ取り早いのだが…。

C コンパイラはこうしている

では、 TL/1-FM コンパイラ以外はどうやっているのか。 調べてみたところ gccMC6809 (FM-7 などが採用しているプロセッサ) 版があり、それを用いて上述の TL/1 コードと同等のコード、すなわち以下のようなコードをコンパイルしてみた。

#include <stdio.h>

void zero();
void one();
void two();

int main(void) {
  zero(12, 23);
  one(34, 45);
  two(56, 67);
}

void zero(void) {
  int x, y;
  printf("%d %d\n", x, y);
}

void one(int x) {
  int y;
  printf("%d %d\n", x, y);
}

void two(int x, int y) {
  printf("%d %d\n", x, y);
}

コンパイルされたコードは以下のようなものであった。

;;; gcc for m6809 : Mar 28 2010 21:13:35 [no tag]
;;; 4.3.4 (gcc6809)
;;; ABI version 1
;;; -mint16
	.module	test.c
	.area .text
	.globl _main
_main:
	pshs	u
	leas	-2,s
	leau	,s
	ldx	#23
	pshs	x	;movhi_push: R:x
	ldx	#12
	jsr	_zero	;CALL: (VOIDmode) (2 bytes)
	leas	2,s
	ldx	#45
	pshs	x	;movhi_push: R:x
	ldx	#34
	jsr	_one	;CALL: (VOIDmode) (2 bytes)
	leas	2,s
	ldx	#67
	pshs	x	;movhi_push: R:x
	ldx	#56
	jsr	_two	;CALL: (VOIDmode) (2 bytes)
	leas	2,s
	leas	2,s
	puls	u,pc
LC0:
	.ascii "%d %d\n\0"
	.globl _zero
_zero:
	pshs	u
	leas	-4,s
	leau	,s
	ldx	2,u
	pshs	x	;movhi_push: R:x
	ldx	,u
	pshs	x	;movhi_push: R:x
	ldx	#LC0
	pshs	x	;movhi_push: R:x
	jsr	_printf
	leas	6,s
	leas	4,s
	puls	u,pc
	.globl _one
_one:
	pshs	u
	leas	-4,s
	leau	,s
	stx	,u
	ldx	2,u
	pshs	x	;movhi_push: R:x
	ldx	,u
	pshs	x	;movhi_push: R:x
	ldx	#LC0
	pshs	x	;movhi_push: R:x
	jsr	_printf
	leas	6,s
	leas	4,s
	puls	u,pc
	.globl _two
_two:
	pshs	u
	leas	-2,s
	leau	,s
	stx	,u
	ldx	6,u
	pshs	x	;movhi_push: R:x
	ldx	,u
	pshs	x	;movhi_push: R:x
	ldx	#LC0
	pshs	x	;movhi_push: R:x
	jsr	_printf
	leas	6,s
	leas	2,s
	puls	u,pc

基本的には cdecl だが、最初の引数だけは x レジスタ経由で渡していることがわかる。 いわゆる fastcall の一種といえるだろう。 しかしこれでは TL/1-FM の挙動と同じではなく、参考にならない。

今回、この出力結果を読み解くにあたって MC6809 の命令セットなどを調べたのだが、興味深いことに MC6809 はふたつのスタックポインタを持っている。 S レジスタが基本的なスタックポインタとして用いられるが、 U レジスタも S レジスタと同じ能力を持っている。 サブルーチンの呼出の際にリターンアドレスを積むときは S レジスタの方が使われるということだけしか差がないようだ。 gccMC6809 版では U レジスタX86 でいうところの BP レジスタと同じようにしか使っていないが、もっと変則的な使い方もできるのかもしれず、それを活用した効率的な呼出規約があるのかもしれない。

Document ID: fdfa7c554d56db3aa97fba03d48a38fe

TL/1 のエッジケース

プログラミング言語 TL/1 について、予約語と同じ名前の変数 (または関数や手続き) を宣言して使うことが可能であるということは以前に紹介した。 その規則には仕様書からは読み取れない若干の曖昧さがあるのではないかという意見を述べていたところ、当時の実際の処理系 (TL/1-FM) で様々な例を試した結果をコメントで貰ったのでひとつひとつ検証していこうと思う。

うまく動く例1

VAR VAR
ARRAY ARRAY[2]
BEGIN
  ARRAY[1]:=123
  ARRAY[2]:=234
  VAR:=ARRAY[1]+ARRAY[2]
  WRITE(0:VAR,CRLF)
END

これについては基本的な動作であり、特に考察が必要な要素はない。 ARRAY という名前の配列が宣言された時点でそれ以後は ARRAY は配列名として解釈される。

うまく動く例2

VAR VAR
ARRAY VAR[2]
BEGIN
  VAR[1]:=123
  VAR[2]:=234
  VAR[0]:=VAR[1]+VAR[2]
  WRITE(0:VAR[0],CRLF)
END

名前 VAR は単変数と配列の両方で宣言しているが、このとき単変数ではなく配列として機能する。 これも仕様通りの動作で、特に疑問なことはない。

以前に取り上げているが、複数の意味で宣言されているときにどれが優先されるかは仕様に明記されていて、単変数よりは配列が優先される。

TL/1 の変なところ (名前解決) - 主題のない日記

コンパイルエラーの例1

VAR VAR
ARRAY VAR[2]
BEGIN
  VAR[1]:=123
  VAR[2]:=234
  VAR:=VAR[1]+VAR[2]
  WRITE(0:VAR,CRLF)
END

エラーの内容

VAR VAR
ARRAY VAR[2]
BEGIN
VAR[1]:=123
VAR[2]:=234
VAR:=
Error : in 60

VAR を配列ではなく単変数で扱おうとしている個所でエラーになっている。 上で述べたように、単変数より配列としての解釈が優先されるので仕様通りであり、不自然なところはない。

コンパイルエラーの例2

VAR ARRAY
ARRAY FOO[2]
BEGIN
  FOO[1]:=123
  FOO[2]:=234
  ARRAY:=FOO[1]+FOO[2]
  WRITE(0:ARRAY,CRLF)
END

エラーの内容

VAR ARRAY
ARRAY FOO[
Error FOO in 20

これについては仕様からは読み取れなかった挙動である。 宣言がいつから有効になるのかという点について、個人的には BEGIN の直後からとするのが曖昧さが少ないと考えていたのだが、実際には宣言の直後からだったようだ。

コンパイルエラーの例3

VAR ARRAY
ARRAY:=123
BEGIN
 FOO[1]:=123
 FOO[2]:=234
 ARRAY:=FOO[1]+FOO[2]
 WRITE(0:ARRAY,CRLF)
END

エラーの内容

VAR ARRAY
ARRAY:=123
BEGIN

Error ARRAY in 30

これについては興味深い。 ARRAY:=123 に相当するオブジェクトコードは正しく生成されているそうなので、そこまでは正しいプログラムだとしてコンパイラは許容しているということになる。 おそらくは、複文 (仕様書にない用語だがBEGIN/END 、またはその他の文括弧で囲んだ複数の文を便宜上こう呼ぶことにする) の扱いについて仕様に書かれていない解釈が存在するのだと思う。

複文は複数の文をまとめたひとつの文であるので、文が現れることが出来る個所にはどこにでも現れることが出来る。 しかし、主プログラムや副プログラムの本体部分はたとえ文がひとつであろうとも BEGIN/END で囲まなければならず、他の文括弧を用いることもできない。 それが仕様書から読み取れる仕様である。

しかし、実際の挙動は主プログラムや副プログラムの本体部分は単に「文」であればなんでも良いのではないか。 他の文括弧を使った複文でも通りそうな気がする。

引数なし関数呼び出しの次の行の解釈でうまく動く例1

FUNC FOO
VAR X
BEGIN
  X:=FOO
  (X)
  WRITE(0:X)
END
FOO
BEGIN
 RETURN 2
END

実行結果

2

TL/1 は行指向ではない (改行は単に無視される) ので、関数名と引数の間に改行を入れても関係なく引数として渡されると解釈すべきで、定義より多く引数を渡す分には変数が参照されることはないのだから問題なく動作するのだろう。

引数なし関数呼び出しの次の行の解釈

FUNC FOO
VAR X
BEGIN
 X:=FOO
 {X:=1}
 WRITE(0:X)
END
FOO
BEGIN
 RETURN 2
END

実行結果

1

{} のかわりに () を文括弧として使った場合

FUNC FOO
VAR X
BEGIN
 X:=FOO
 (X:=1)
 WRITE(0:X)
END
FOO
BEGIN
 RETURN 2
END

エラーの内容

FUNC FOO
VAR X
BEGIN
X:=FOO
(X:=
Error : in 50

関数名の直後の開き丸括弧は常に引数の開始であると解釈するようだ。 当時のコンピュータの能力を考えると少ない先読みでトークンの意味を確定しようとする設計は合理的と言える。 おそらく TL/1 の文法を分類するなら LL(1) の範疇に収まるのではないかと思う。 一方で、そもそも丸括弧を式括弧にも文括弧にも引数にも用いようとしているのが解釈し難さの原因でもあるので最初から分けておくべきだったとも思う。

引数付関数呼び出しを2行に分けて記述してうまく(?)動く例

FUNC FOO
VAR X
BEGIN
  X:=FOO
  (1)
  WRITE(0:X)
END
FOO
VAR Y
BEGIN
 RETURN 2+Y
END

実行結果

3

これは少しばかり奇妙な挙動だと思う。 スタックレイアウトが想像できない。

Document ID: 6ed36823564fc58efd50e2fd7816e296

響き

言葉というものについて、その意味を忘れて響きだけを感じたときに全く別の印象を持つことがある。 まるで外国の言葉のようにも感じられたりする。 たとえば2ちゃんねるの「ドイツっぽく便意を伝えたい」というスレッドで「フンバルトデルベン」というネタが高評価であった。 確かにドイツ語らしい響きを感じる。

私が常々ドイツ語らしい響きのある言葉だと思っているのは「立憲君主」だ。 カタカナでリッケンクンシュと書くとドイツを感じる。 首脳会議をシュノー会議と書くとシュノーという地名がフランスあたりにありそうな気持ちになる。 古民家などはロシア風に感じる。 おそらくはトロイカの響きと似ているからだろう。

逆に外国で日本らしい響きと感じられる音もあるようだ。 外国の (外国語の) 小説に登場する日本人の名前が実際にはとてもありそうにない、しかし響きは日本風であるという事例をいくつか見たことがある。 まあ日本語の場合はわかりやすいだろう。 基本の音は常に子音と母音が合わさっていて、拗音や促音が少し混ざる感じだ。 要は母音がいっぱい入っていれば日本語風になる。 (実際には母音がかなり弱く発音される場合もあるが。)

特に外国語に詳しいわけでもないのにそういう印象が生じてしまうのは面白い現象だと思う。

Document ID: a7c80f3b0f3b75a876b0209ae527e98f