【再掲】文芸的プログラミングが普及しなかった理由

Ichi Kanaya
Pineapple Blog
Published in
10 min readJun 9, 2017

この問題を考え始めたきっかけは1995年の京大の入試問題だった.こんな問題だ.

として

とする.設問は,好きな自然数nをひとつ決めよ.関数g(n)の値をこの設問の得点とするというものだった.ヒントとして

の関係があげられている.

この問題を@dankogai氏が

とワンライナーで解いている.問題文に合わせるためにスクリプト中のfgに置き換えると

$ ruby -e '(0..13).each{|n|puts "g(#{n})=#{3*(((1..7).map{|k| k**n}.inject(0){|s,i|s+i})%7)}"}'

である.これはSchemeで素直に書いても

(use srfi-1)
(define (f n)
(remainder n 7))
(define (g n)
(* 3 (f (fold (lambda (k a) (+ a (expt k n))) 0 (iota 7 1)))))
(print (map g (iota 13 1)))

とそれほど複雑にはならない.だがこれをPascalで書くとこの長さになる.(整数型であるintegerではなく浮動小数点型のrealを使っているのは,標準Pascalだとオーバーフローするため.)

program kyoto;

uses math;

function f(n: real): real;
var
r: real;
d: real;
begin
r := floor(n / 7);
d := r * 7;
f := n - d
end; { f }

function g(n: integer): real;
function expt(k, n: integer): real;
var
i: integer;
r: real;
begin
r := 1;
for i := 1 to n do
r := r * k;
expt := r
end;
var
k: integer;
a: real;
begin
a := 0;
for k := 1 to 7 do
a := a + expt(k, n);
g := 3 * f(a)
end; { g }

var
i: integer;
begin
for i := 1 to 11 do
writeln(g(i))
end.

現代的なプログラミング言語を使うことは,プログラムを短くする効果がある.短いプログラムは読みやすいし,エラーを混ぜ込むチャンスも減るので好ましいものだ.

しかし,汎用的なプログラミング言語を使っている以上,いつでもプログラムを短くできるわけではないし,十分複雑な問題に対しては,プログラムはいつでも心理的に耐え難いほど長くなるものだ.この問題へのアプローチのひとつが「文芸的プログラミング」である.

ドナルド・E・クヌース「文芸的プログラミング」

文芸的プログラミングとは計算機科学の大家ドナルド・E・クヌースが始めた活動で「本を書くようにプログラムを書こう」という運動とそのためのツールである.実際,クヌースが書いたTeXプログラムは文芸的プログラミングスタイルで書かれていて,TeXプログラム自体が TeX: The Program という書籍になっている.

クヌースが作成した文芸的プログラミングをサポートするツールはWEBと言って,PascalとplainTeXを交ぜ書きできるものだった.クヌースはその後PascalをCに置き換えたCWEBというツールも共同開発している.

Pascalはワンパスでコンパイルできるように文法が設計されていたこともあり,Pascalプログラムは人間でも頭から読んでいけば一応は理解できるのだが,先程示した通り,実用的な問題を解くにはかなりプログラムが長くなる.どうしても,プログラムを「適切な」断片に分割し,かつ心理的に「正しい」順序で断片を並べ直さないと,非常に読みにくいものになってしまう.

PascalをCに置き換えても状況はさほど改善しない.Cで書けばプログラムを断片に分割するのは簡単になるが,その代わりプログラムのストーリー性は失われる.(そしてC++を選べば両方を失うことになる.)

そこでクヌースはWEBに単純なマクロを導入して,プログラムの切り貼りを可能にした.プログラムはプリプロセッサによってコンパイラが読める順序に展開される.CWEBではCプリプロセッサがそのまま用いられる.

先程紹介したPascalプログラムをWEBで書き直すと,例えば次のような書き出しになる.

@* The program outline. Ok, let’s have a bird’s view of the program at first. Since we need to write down $g(i)$ where $i\in\{1,2,\dots,11\}$ eventually, our program should look like this.
@p program kyoto;
@<Libraries to be imported.@>;
@<Definition of function |f(n)|.@>;
@<Definition of function |g(n)|.@>;
var i: integer;
begin
for i := 1 to 11 do
@<Write |g(i)| on the screen.@>;
end.

太字部分がマクロで,プログラムのどこかでその定義が書かれなければならない.このWEBプログラムを書き上げてコンパイルすると次のようになる.

確かに読みやすいし「文芸的」ではあるだろう.しかしそのソースコード全文はこうである.

\def\title{Kyoto}@* The program outline. Ok, let’s have a bird’s view of the program at first. Since we need to write down $g(i)$ where $i\in\{1,2,\dots,11\}$ eventually, our program should look like this.
@p
program kyoto;
@<Libraries to be imported.@>;
@<Definition of function |f(n)|.@>
;
@<Definition of function |g(n)|.@>
;
var i: integer;
begin
for i := 1 to 11 do
@<Write |g(i)| on the screen.@>;
end.
@ We start with the most simple part of this program. Writing $g(i)$ on the screen is quite simple task as follows. (For those who are unfamiliar with Pascal, and also for demonstrating parametric macro of WEB system, we insert definition of |print_string| macro here.)
@d print_string(#)==writeln(#) { Printing on the screen. }
@<Write...@>==pirnt_string(g(i))
@ The function $f$ is given as $f(n)\equiv n\bmod7.$ Since $n$ can be very big, and Pascal's |integer| cannot handle such big numbers, we need to implement |f(n)| by using |real| as follows.@<Definition of function |f(n)|.@>=
function f(n: real): real;
var
r: real;
d: real;
begin
r := floor(n / 7);
d := r * 7;
f := n - d
end;
@ In the above section, we used |floor| function form the math library.
@<Libraries...@>=uses math;
@ Now let's think about the function $g$. Remember the function $g$ is given as $g(n)\equiv3f\left(\sum_{k=1}^7k^n\right),$ the outline of the function can be as follows.@<Definition of function |g(n)|.@>=
function g(n: integer): real;@#
@<Definition of function |expt(x)|.@>;
var
k: integer;
a: real;
begin
a := 0;
for k := 1 to 7 do
a := a + expt(k, n);
g := 3 * f(a)
end;
@ Due to lack of the exponential function in standard Pascal, we must implement $\exp(x)$ by ourselves.@<Definition of function |expt(x)|.@>=
function expt(k, n : integer): real;
var
i: integer;
r: real;
begin
r := 1;
for i := 1 to n do
r := r * k;
expt := r
end;

現代の感覚でいうと,これはあまりにも使いづらいものだ.今時Cプリプロセッサ程度のマクロ機構に全面的に頼るようなプログラムが書けるだろうか.結局のところ,思うに,マクロ機能があまりに単純すぎたために,クヌースのような天才的ハッカーでないと使い物にならなかったのではないだろうか.

プログラミング言語はCommonLISPのように超強力なマクロ機構を目指すか,Schemeのように「衛生的な」マクロ機構を目指すか,あるいはHaskellのように頭からマクロ機構を否定するかに行き着く気がする.WEBにはその全ての姿勢が欠けているために,流行らなかったのではないだろうか.

【追記】スラドでACさんから次のコメントを頂いた.

向こうにコメントするべきかもしれない & 調査済みかもしれませんが、Haskellには文芸的プログラミングのサポート機能があります。
が、個人的にはLaTeX構文は辛いです。

https://www.haskell.org/onlinereport/literate.html

言語による文芸的プログラミングのサポート機能で一番使えそうと思ったのはmarkdownで書ける、coffeescriptですが、coffee自体が廃れているので微妙ですね。

http://coffeescript.org/#literate

--

--