プログラミング
Lispの読みにくさの原因とは?
What makes Lisp difficult to read? (paultm.nl)
要約
Lisp言語ファミリーは、その括弧(パーレン)を多用する構文で知られていますが、多くのプログラマーにとって読みにくさの原因となっています。この記事では、単なる慣れの問題だけでなく、認知科学の知見に基づき、括弧の配置がコードの解析にどのような影響を与えるかについての理論を提示しています。特に、近接性や対称性といったゲシュタルトの法則が、コードの構造理解にどのように寄与するかを論じています。
全文翻訳
Lisp言語ファミリーは、その括弧を多用する構文で悪名高いです。私を含め、多くの人々は、Python、Java、Cのような言語の記法よりもこの構文を読むのが難しいと感じています。Lispの読みにくさは顕著であり、Lispの普及を助けるために新しい記法を作成する試みが複数行われてきました。しかし、これらの試みにもかかわらず、そしてLispがコンピューティングサイエンス自体とほぼ同年齢であるにもかかわらず、なぜLispが読みにくいと認識されるのかについて、完全に満足のいく説明にはまだ出会っていません。
Lisp開発者は一般的に、これは単なる慣れの問題であり、Lispの記法はCの記法ほど一般的ではないため、プログラマーはそれを読むことに慣れていないと主張します。一度Lispでプログラミングに慣れると、括弧に気づかなくなる、とされています。私はこれ以上のことがあると信じており、より深い調査によって、心地よい構文を設計する方法についての洞察を得ることができるかもしれません。この記事では、Lispの記法、特に括弧の配置が解析に余分な労力を必要とする理由についての私の理論を、認知科学からのいくつかの観察に触発されて説明します。
Lispの記法に慣れていない方のために説明すると、典型的なLispプログラムでは、すべてのステートメント、式、関数呼び出しは同じ構文で書かれます:(operation arguments...)。
Lisp(Scheme)での再帰的な階乗関数の定義と、JavaScriptでの同等の関数を比較してください。
(define (factorial n) (if (= n 1) n (* n (factorial (- n 1))))) Lispスタイルの階乗定義(Scheme)
function factorial(n) {
if (n == 1) {
return n;
} else {
return (n * factorial(n - 1));
}
}
Cスタイルの階乗定義(JavaScript)
Lispのサンプルには、中置記法 (n == 1)、キーワード (else)、および異なる種類の区切り文字 ({}) が欠けていることに気づくでしょう。これらの違いは、読みにくさの議論で最も注目される傾向があります。これらの記法が読みにくさにいくらか影響を与えることは同意しますが、その重要性は誇張されていると私は疑っています。慣れを超えて、(- n 1) が (n - 1) より明らかに劣っているとは思いません。
Cスタイルの言語がこれらの異なる種類の記法でアプリケーションを持つことは、読みにくさに大きく影響する可能性があります。私はこの側面を別の投稿で調査するつもりです。この記事では、主に通常の関数呼び出しを比較した場合に残る違いに焦点を当てます。つまり、operation(arguments ...) は (operation arguments...) よりも心地よく見えるのはなぜでしょうか。
人間の知覚におけるハードウェアアクセラレーション
括弧の配置がなぜ重要なのかを理解するには、人間の知覚がすべての入力を平等に扱わないことを考慮する必要があります。これを体験するには、以下の単語の表示色をできるだけ早く声に出して言ってみてください。
あなたは、2行目、つまりテキストの色が書かれた色と一致しない方が読むのが遅いことに気づくでしょう。これはストループ効果として知られています。このような効果がいくつか発見されており、視覚処理タスクを妨げたり助けたりする可能性があります。私はこれらの効果を、私たちの知覚機械に組み込まれた一種のハードウェアアクセラレーションと考えています。1 プログラミングと同様に、ハードウェアの機能の最善を尽くすべきです。
例えば、以下の同じデータセットの2つの視覚化を考えてみてください。私はデータから2つの外れ値を除外しました。これらの視覚化の1つは、外れ値を見つけやすくするために「ハードウェアアクセラレーション」トリックを使用しています。
あなたは間違いなく、2番目の視覚化で外れ値を見つけるのが、最初の視覚化よりもずっと早いでしょう。実際、プレーンなデータ表示では検索タスクは O(n) かかりますが、ハードウェアアクセラレーションされた検索は奇跡的に O(1) で終了します。優れたデザインは、この効果を利用して、例えば異なる形状のアイコンや強調色によってインターフェースを検索しやすくします。
ストループ効果とポップアウト効果を人間のハードウェアアクセラレーションを説明するために選択したのは、これらの効果が顕著で再現しやすいからです。残念ながら、認知科学からの知見はすべて、明白または明確に定義されているわけではありません。そのような効果の適用には、ある程度の創造性と主観性が必要です。したがって、読みにくさが完全に客観的に議論される認知効果によって説明されると示唆するつもりはありません。2むしろ、これらの効果をデザイン原則として呼び出し、私自身の観察を支持するために使用します。
引数の近接性
記法に戻ると、構文の主な目的は、トークンがどのようにグループ化されるかを示すことです。ゲシュタルト心理学は、私たちがグループ化をどのように知覚する傾向があるかについて、いくつかの方法を説明しています。最も具体的なものの一つは、近接性のゲシュタルトの法則です。この「法則」は、互いに近いオブジェクトは関連していると知覚されるという観察です。例えば、ここでも同じデータの2つの提示があります。データポイントを2つのグループに分割する方法を見つけてください。
2番目の視覚化は、データがx軸に沿って分割された2つのグループで構成されていることを明確に示唆しています。近接性の法則によって説明される「ハードウェアアクセラレーション」は、生のデータよりもこの分割を早く知覚できるようにします。
括弧とコンマの配置によって作成されるスペースにより、Cスタイルの記法はLispスタイルの記法よりも近接性の法則に従うことが多くなります。引数が1つだけのCスタイルの関数呼び出しは、スペース文字を必要としません。これにより、そのような呼び出しを単一の視覚的なグループとして認識できます。対照的に、複数の引数を持つ呼び出しは、各引数をコンマとスペース文字で区切ります。Lispスタイルの呼び出しには、変化するスペースはありません。すべての関数と引数は同じスペース文字で区切られます。したがって、スペースはグループ化を伝えるのにほとんど役立ちません。
例えば、交互の記法で同じ関数呼び出しを2つ考えてみてください。3番目と4番目のサンプルは、最初の2つのぼやけたバージョンであり、近接効果を強調しています。
(AAAAA (BBBBB (CCCCC)) (DDDDD) (FFFFF (GGGGG (HHHHH)))) Lisp
AAAAA(BBBBB(CCCCC()), DDDDD(), FFFFF(GGGGG(HHHHH()))) C
(AAAAA (BBBBB (CCCCC)) (DDDDD) (FFFFF (GGGGG (HHHHH)))) Lisp (ぼやけた状態)
AAAAA(BBBBB(CCCCC()), DDDDD(), FFFFF(GGGGG(HHHHH()))) C (ぼやけた状態)
Cのスニペットがぼやけたときに3つの塊で構成されていることに注意してください。これらの塊は、周辺視野でさえ、コードのおおよその構造を伝えます。Lispのサンプルでは、7つの識別子すべてが個別の塊にぼやけており、周辺視野でのグループ化の兆候はありません。
区切り文字の近接性
括弧のグループ化に関連する他の2つのゲシュタルトの法則は、閉鎖性と対称性の法則です。閉鎖性の法則は、形状の断片は、断片間のギャップを心の中で埋めることによって単一のエンティティとして知覚されると述べています。つまり、2つの括弧は単一の楕円形を形成するため、視覚的なグループを形成します。対称性の法則は、単純に対称的なコンポーネント(例:区切り文字)が同じ視覚グループの一部として知覚されると述べています。
閉鎖性の法則:単一の形状(円)は、そのコンポーネント間のギャップを埋めることによって知覚されます。
対称性の法則:対称的な形状は、一緒に属するものとして知覚されます(近接性の法則を包含)。
これらのグループ化効果を利用するには、対応する括弧の形状が明確に区別できる必要があります。実際、ほとんどの場合、これはそうではありません。網膜の光受容体の解像度が最も高い部分は、視野の約2度にしかすぎません。これは、モニターから70センチ離れた場所では、約2.5センチメートルの直径の領域しか快適に読むのに十分なシャープさがないことを意味します。あなたの脳はこのぼやけたテキストを隠しますが、目を動かさずに数語先を読もうとすると見ることができます。
Lispは関数名を開き括弧の後に置くため、括弧間の距離が長くなります。これにより、括弧が単一の形状としてスキャンされるのに十分な近さになる可能性が低くなります。Lispはmake-string-output-streamやcall-with-current-continuationのような長い関数名を持つ傾向があるため、余分な距離はかなり大きくなる可能性があります。それを説明するために...