Contents
空白で構造を示す
人間には、位置的に近いものをまとまりと認識する性質がある。 これを利用すると、意味の近いものを位置的にも近づけることにより、 意味的なまとまりを人間に認識させることができる。
「ノンデザイナーズ・デザインブック」では、これを「近接の原則」と呼んでいる。 また、組版にも同様な原則がある。
プログラムにおいて、まとまりを示すために使用できる空白には主に以下のものがある。
- 改行
- インデント
- 行内空白
なお、行末空白はプログラムを表示した時に存在するかどうか認識できないことが多いため、 それによって意味が変化するのはプログラムを理解困難にする。
手間の節約
思考の節約
- 人間が認識するまとまりとプログラム構造のまとまりが一致するとプログラムの理解が容易
例
例: Ruby の改行とセミコロン
Ruby では、文の終端は改行かセミコロンで示される。
改行は文が終端可能な箇所のみで文の終端として認識され、 それ以外の箇所では無視される。
これは改行が区切りを示すという人間の認識を利用した規則とみなせる。
ちなみに、文が終端できない箇所にセミコロンがあった場合にはシンタックスエラーになる。
例: インデントでブロック構造を表現するプログラミング言語
- Occam
- Python
- Haskell
例: YAML, JSON, XML とインデント
YAML はインデントを強制する。 YAML の文法はインデントを用いて構造を表現する仕様である。
JSON はインデントを許す。 JSON は JavaScript の記法に由来するため、 JavaScript と同様にインデントが可能である。 文字列はクォートで括られるため、 それ以外の箇所では比較的自由に空白を挿入することができ、意味を変えることなくインデントできる。
XML はインデントを困難にする。 空白が PCDATA に含まれて、インデントを変えると意味が変わる可能性がある。 そのため、一般にはインデントは行いにくい。 タグの中で改行すれば不可能ではないが、タグというひとつのまとまりが行をまたがるため、 それも2次元構造中の位置でまとまりを示すという意図に反する。
例: merd での空白の無い部分式 (行内空白)
merd では 1+2 * 3 は (1+2)*3 と解釈される。
これは、空白の無いトークンの並びがひとつのまとまりとして解釈されるからである。
この規則は人間が空白によって認識する構造を処理系が理解し、それに従う規則とみなせる。
例: Ruby におけるメソッド引数と二項演算子のあいまいさの解決 (行内空白)
Ruby では、メソッド呼び出しで引数を囲むカッコを省略できる。 この場合でも、引数があればメソッド名の後に記述するが、 引数が無ければメソッド名でメソッド呼び出しは終端する。
このとき、メソッド名の後に、式の始まりとも 2項演算子ともみなせる文字があった場合、曖昧さがおこる。
たとえば、obj.m+1 という式は、obj.m(+1) と (obj.m)+(1) というふたつの解釈がある。 前者は + を単項演算子として式の始まりとみなしており、後者は 2項演算子とみなしている。
Ruby はこのあいまいさを + の左右の空白の有無で解釈を変える。
- obj.m+1 は (obj.m)+(1) とみなされる
- obj.m + 1 は (obj.m)+(1) とみなされる
- obj.m+ 1 は (obj.m)+(1) とみなされる
- obj.m +1 は obj.m(+1) とみなされる
つまり、+ の左に空白が有り、右に空白が無い場合、+ は右側のまとまりの始まり、 すなわち式の始まりであると判断され、単項演算子と解釈される。
これにより、空白により人間が認識する +1 というまとまりと Ruby が解釈するまとまりが一致する。
この規則が適用されて解決される曖昧さには + 以外に以下のものがある。
- * の二項演算子と残余引数の曖昧さ: obj.m*[1,2,3]
- & の二項演算子とブロックを渡す引数の曖昧さ: obj.m&lambda{}
- - の二項演算子と単項演算子の曖昧さ: obj.m-1
- / の二項演算子と正規表現の曖昧さ: obj.m/1#/ (最初の / のところで obj.m(/1#/) という正規表現を引数とするか、obj.m() / 1 という割り算か判断する。割り算とした場合 1 よりも後はコメントになるようにして、両方とも文法的には正しいようにしてある。ただしこれは常にそうなるわけではなく、syntax error になるほうを空白による判断で選んでしまうこともある。)
- % の二項演算子と %記法のリテラルの曖昧さ: obj.m%q#x# (obj.m(%q#x#) という文字列を引数とするか obj.m() % q という q という変数で割った余りの両方の解釈が可能。この例では余りと解釈した場合、q 以降はコメントとして無視されるようにしてある。)
例: Ruby におけるメソッド引数とインデックス参照のあいまいさの解決 (行内空白)
Ruby では、a[0] という式は、a というメソッドの引数に [0] という配列リテラルが与えられた式か、 a というメソッドの返り値にインデックス参照を適用するのか曖昧である。
この曖昧さは、空白によって以下のように解決される。
def a(*x) [1] + x end def m1() a[0] end; p m1 #=> 1 def m2() a[ 0] end; p m2 #=> 1 def m3() a [0] end; p m3 #=> [1, [0]] def m4() a [ 0] end; p m4 #=> [1, [0]]
つまり、メソッド名と "[" のあいだの空白の有無により、配列リテラルかインデックス参照かが決定される。
これは、括弧なしのメソッド呼び出しでは、ほとんどの場合メソッド名の直後に空白を入れるという慣習と一致する。
例: Ruby で空行を文の終わりとする実験とその失敗
例: FORTRAN66
FORTRAN には、ループを表す DO 文があり、 たとえば、以下のように書くと、行番号が10番までの行をまとまりとして、変数 I を 1から 5 まで変えて繰り返し実行する。
DO 10 I=1,5
ここで、カンマをピリオドに間違えて以下のように書いた場合、 空白が無視され、DO10I という変数に 1.5 を代入することになる。
DO 10 I=1.5
これがプログラマにとって意図しない結果なのは、 人間が空白によって "DO", "10", "I" が異なるまとまりであると認識するのに対し、 処理系がそれに反して空白を無視した解釈を行うためである。
参考文献
- The Non-Designer's Design Book: Design and Typographic Principles for the Visual Novice
- Robin Williams
- Peachpit Pr
- ISBN 978-1566091596
- ノンデザイナーズ・デザインブック [フルカラー新装増補版]
- Robin Williams (著), 吉川典秀 (翻訳)
- 毎日コミュニケーションズ
- ISBN 978-4839928407
- merd's syntax breaktrough
- RFC 4627: The application/json Media Type for JavaScript Object Notation (JSON)
- D. Crockford
- http://www.ietf.org/rfc/rfc4627.txt
- YAML: YAML Ain't Markup Language
参考
- 視覚心理学でいう群化
- 視覚心理学への招待 〜見えの世界へのアプローチ〜
- 大山 正
- サイエンス社 (2000)
- ISBN 978-4-7819-0963-9
- ゲシュタルトの法則
- 視覚の情報処理 <見ること>のソフトウェア
- K.T.Spoehr and S.W.Lehmkuhle (苧阪直行 他 訳)
- サイエンス社 (1986)
- ISBN-13: 978-4781904665