Island Life

< ピアノレッスン57回目 | 発表会、観客 >

2012/08/23

S式の読みやすさ

空き時間ができるとCコードをS式 (cise) にちくちく直しては 「読みやすくなった」と悦に入ってる自分にとっては、 「S式は読みにくいから新しい構文考えようぜ」という議論は出発点からして別世界なのだけれど、 こんなのを見たのでまたちょっと考えてみた。

LisperにとってS式がいちばん読みやすい (そして非Lisperにとって読みにくい) 理由についての 私見は以前書いた。(WiLiKi:Lisp:S式の理由)。

ただ、そこに書いたことが全てではないとも思っている。

というのは、Haskellのコードも読みやすいと感じるからだ。 遅延評価のセマンティクスや抽象化された型の解読で頭を捻ることはまだ多いけれど、 構文で悩むことはあまりない (関数適用の優先順位が高い、ということさえ忘れなければ)。 S式は読みやすいコードの十分条件であっても必要条件ではなさそうだ。

典型的なHaskellのコードがインデントとパターンマッチによって、 「木構造の素直な表現」になっている、という点は重要だ。 ただ、同じインデント構文でもSRFI:49は全く見やすいと思わないので、 木構造であることは必要条件ではあるが十分条件ではなさそうだ。

(インデント構文といえばPythonもそうだが、あれがHaskellほどには読みやすく 思えないのは、副作用がちょくちょく出てくるために木構造として把握できない (制御が根から枝ではなく、上から下になってる) せいかもしれない。)

もうちょい考えてみると、Haskellの読みやすさに大きく貢献しているのは そのコンパクトさであるように思う。 木構造は脳内表現として使う分には良いが、ダンプした場合に、 葉の近くが重くなると根の部分の幅が広がりすぎる(ソースコードでは各子供が 縦に遠く離れる)。頭の中だと自由にズームできるからいいんだけど。

それを防ぐには木を適切な大きさで分解して名前をつけ、「森」にするのが常道だが、 あまり細かく分割しすぎても見通しが悪い。 個々の木に十分な情報量を持たせたまま木の縦幅を一定程度に納めるには、 コードの密度が重要になる。

実は、中置記法の読みやすさというのも、「中置だから」ではなく 密度によるところが大きいんじゃないかと最近思う。 識別子に記号を使えない言語では、記号による演算子と識別子の間に空白をおく必要が無いので 高密度に書ける。 (Unbounded spigot algorithmのHaskell版 comp とScheme版 lft*lft の差が顕著)

それだけではなく、式の木の末端を空白なしで書き、 根に近い方で適宜空白を入れることで、木構造を密度の濃淡で表現できる

(-b + sqrt(b*b-4*a*c)) / (2*a)

Lispでもマクロで中置記法を使えるようにすることは簡単だが、*+などの 記号が識別子の一部にもなり得るので、各トークンの間に空白を置かねばならない。

(- b + sqrt(b * b - 4 * a * c)) / (2 * a)

密度が下がるばかりではなく、密度の濃淡で木構造の表現することも難しくなる。 これが、「Lispで中置記法してもあまり嬉しくない」理由かもしれない。

だから、S式を改良しようというなら、単に括弧を減らして中置記法にするだけではだめで、 コードの密度を上げる&適宜濃淡をつけられる手段を考えなければダメなんじゃなかろうか。

Tags: Programming, Lisp, Haskell

Past comment(s)

jmuk (2012/08/24 14:28:36):

たしかmerdは、空白の有無で優先順位を表現できますね。 a*b + 1 は (a * b) + 1 だけど、 a * b+1 は a * (b + 1)になる。 http://www.kmonos.net/alang/etc/merd.php#wysihiip

……とはいえ、個人的にはこういうのはあまり好きではありません。今の仕事のプロジェクトでは演算子の前後に必ず空白を入れるコーディング規約ですが、これは妥当だと思います。 中置記法が好きなのは、個人的には子ノードとの物理的距離が近いことですね。 b * b - 4 * a * c と比べると、 (- (* b b) (* 4 a c)) の場合、マイナス記号と第二項の距離が遠くて認識に時間がかかります。 もちろん、関数呼び出しでは気にならないので慣れの問題なんでしょう。また、式自体が長くなると距離がいずれにせよ長くなり、避けたくなります。 一方、Haskellの場合にも if e `elem` lst ... のように、単純な関数では中置記法で書くことが多いのも水平距離の問題かなぁと思っています。

shiro (2012/08/24 18:32:50):

うむ。子ノード間の水平距離が開くと読みづらい、というのも、上で書いた垂直距離が開くと読みづらいのと同じだと思う。前置記法支持者が「数式だって複雑になったら中置記法でもわかりづらいぜ」って例をあげる時に、よく2行に折り返さないとならないくらい複雑な式を持ってくるんだけど:

(/ (... some-long-expression ...)
   (... some-other-expression ...))

折り返すと子ノード同士の距離が近くなるという。

あと、「演算子の前後に必ず空白を入れる」って私もC系言語で経験あるんですが、-> や . の前後、 [] や , の前、 単項の * や & の後、などは空白を入れないように思うんですが、いかがですか。このへんでナチュラルなグループ化がなされているような。

そうだ、Haskellでついつい f (x:xs) のつもりで f x:xsと書いてエラー喰らうのはこの「高密度の方が子ノード」というシグナルに騙されるから。

jmuk (2012/08/25 01:39:10):

おお確かに、そういった演算子では空白を入れないですね。単純な二項算術/論理演算子だけなのかな……。haskellのf x:xsは、たしかにいかにもよくやるミスですね。

Post a comment

Name: