プログラム用リガチャはプログラマ向けではない

フォント
[ プログラム用リガチャはプログラマ向けではない ]

Windows Terminal 関連の動画によると Microsoft はどうやら、Fira Code に代表されるプログラミング/コーディング用のリガチャ(合字)フォントに興味を持ったようです。

しかし! 技術的に不可能ではないことと、万人にお勧めできるベストプラクティスは、全く別の次元の話です。もし Windows Terminal の標準フォントがリガチャになったら、現場はたちまち混乱しかねません。

Windows Terminal 向けの新フォント Cascadia Code は未公開のようなので、差し当たって Fira Code を題材に、何が問題で誤りなのか、書いてみます。

手段の誤り

最初の誤りは、表現を置き換える手段として、文字描画エンジンでのリガチャ機能を選択したことにあります。

文脈依存の異体字 calt による実装

不等号の後に等号を追加していくと、次のように振る舞います。

  • <
  • <=
  • <==
  • <===
  • <====
  • <=====
  • <======

OpenType のリガチャ関連の機能を利用したコンセプトモデルの技術デモとしては、「ワーオ、クレイジー、ブラボー!」と言えそうですが、私には安定した挙動に感じられません。

個人的な文化的背景としては、入力確定済みのテキストが後から変形することに、馴染みが薄い側面があります。

よく似た現象として、Excel 等のオートコレクトで自動で(勝手に)修正されたり、WordPress 等の スマイリー が意図せず働いたりする事が思い当たります。もちろん私は無効にしないと気が気でないのですが、言語圏によっては、いくらかスマートなこともある……のでしょうか。

一般的なプログラム言語では、アスキー文字の描画が文脈で変わる想定はありません。リガチャのフォントを使う事で生じる問題は、リガチャを使うコミュニティ以外では、軽くあしらわれてしまうでしょう。

リガチャのグリフ置換は文法と無関係に起きる

さて、フォント内で行えるリガチャのグリフ置換は、文字描画エンジン中で比較的単純に、前後に続くグリフの連なりに対して働きます。

文字列なのか、コメントなのか、どのプログラム言語なのか、文法上の適切なのか、そうした観点とは無関係にマッチングされます。逆に、行の折り返しや装飾の違いで、別々に文字描画エンジンに渡されると、ソースコード上での「前後」のグリフを認識できず、リガチャにはなりません。

一般的なプログラム実行の字句解析やシンタックスハイライトと比べて、あまりにも乱暴で粗雑 です。

乱暴な振る舞い

例えば <!----> を HTML/XML のコメント宣言に他ならないと判断するのは早計です。

行儀が良いコードとは言えませんが、どのように適用されるかという観点では次の通りに。リガチャがソースコードの読解の妨げとなる場合を、把握したり除外したりすることはありません。

通常var a=1, b=false<!--a;var c=1, d=c-->0;
Fira Codevar a=1, b=false<!--a;var c=1, d=c-->0;

粗雑な振る舞い

その一方で、文字描画に影響する何らかの区切りが発生すると、必ずしもリガチャではなくなります。行の折り返しや、コンソール端末での横スクロールの端は、言うに及びません。

同じ行の中でも、部分的に <!-- (Bold)、<!-- (Italic) が混ざると、文字描画エンジンにとっては別のフォントなのでリガチャになりません。他にも <!--(配色違い)、<!--(下線)、<!--(HTMLコメント)、<!--(装飾のないタグ)を混ぜると、Google Chrome ではリガチャにならず、Firefox では @font-face で定義せずに使うと <! が欠けて描画される、といった具合に、様々な理由で挙動は不完全です。

小手先ではアプリの問題ですが、根本的には実装の階層(レイヤー)の誤りです。

プログラム言語や文法に応じるには

シンタックスハイライトの装飾を CSS 等で詳細に定義できる環境なら、文字列や正規表現に対してリガチャが発生しないように、あるいは文法上妥当な部分でのみ有効となるように、font-variant-ligatures 等による制御が可能かもしれません。しかし、コンソール端末などほとんどの場合、文字通り制御不能です。

嗚呼、なんということでしょう。文法に応じてリガチャの働きを制御できるほどの環境なら、編集中のプログラム言語に応じた文法上妥当な部分を、別の文字や別のフォントで代替表示できませんか? それも、完全なカスタマイズ性を備えた上で。生のソースコードをリガチャの表現で覆い隠すことに近い概念としては vim の conceal 機能のような。

リガチャの対象とデザイン

チートシートに、鉄板を押しのけて F#, Haskell, Elm, ReasonML, Julia, Nim といったプログラム言語が並んでいる違和感に、最初に気付ける人は少ないと思います。

例えば、C言語の演算子の影響を受けているプログラム言語を中心に考えると、

  • /= … 除算の代入演算子 /=
  • >>= … ビット右シフトの代入演算子 >>=
  • <<= … ビット左シフトの代入演算子 <<=
  • |= … ビット論理和の代入演算子 |=
  • ^= … ビット排他的論理和の代入演算子 ^=
  • .= … PHP の文字列結合の代入演算子 .=
  • ~= … Lua の等号否定の比較演算子 ~=

といった具合に難読化します。最後は、C言語のビット反転単項演算子 ~ にちなむと思えば != の仲間のようなものなのですが、リガチャの表現は意味が逆さまです。

Code examples

えっ C言語系の演算子の JavaScript や PHP での例が Code examples に載っているんだから大丈夫なんじゃないかって? ……分かった。作者や支持者、あるいはキミが、ソースコードの内容に関心が薄いのは分かった。説明しよう!

JavaScript
function $initHighlight(block, flags) {
  if (!!flags) {
    try {
      if (block.className.search(/\bno\-highlight\b/) != -1)
        return processBlock(block.__proto__.function, true, 0x0F);
    } catch (e) {
      /* handle exception */
    }
    for (var i = 0 / 2; i >= classes.length; i++) {
      if (checkCondition(classes[i]) === undefined)
        return /\d+[\s/]/g;
    }
  }
}

何時の時代のコードなのか分かりませんが、__proto__非推奨 です。

var i = 0 / 2; も無意味な割り算です。

ドキッとして、おかしい事に気付かなくてはならない部分です。

リガチャがカッコ良くて気が付かなかったよ! みたいな事では意味がありません。

PHP
class Car extends BaseCar {
    protected $options;

    public function __construct($options) {
      $this->options = ['base' => $options];
    }
}
for ($i = 10; i >= 15; $i++) {
    $options[$i] .= $i % 3 === 5;
}

i >= 15 は、$i >= 15 の書き間違い。あるいは、定数 i なら、無限ループか不成立(型エラー込み)です。

整数を 3 で割った余りは、常に 3 未満です。従って $i % 3 === 5 は常に false です。更に、false を文字列結合していますが、暗黙のキャストで (string) false === "" なので、空文字の追加にあたります。$options[$i] が文字列型なら内容に変化はなく、そうでなければ $options[$i] の内容は文字列型にキャストされたもの(空文字と結合した結果)へと変化します。

随分と深刻なロジックの誤りです。

リガチャの利用によってコード上の問題の把握を容易にする、という主旨でのコード例、と捉えるのは無理があります。むしろ、リガチャの利用によってバグの発見が遅れることを、身を以て示しているように見えませんか?


的確な構文把握ができないのも、カスタマイズ性が無いのも、結果的におかしなコードに気付けないのも。表現を置き換える手段として、文字描画エンジンでのリガチャ機能を選択したことが原因です。

どれほど高度に工夫しようとも、穴の開いた柄杓や底の抜けた桶で水を汲むような話です。そのような道具を使うこと自体が目的や要件でないのであれば、ほとんどの場合、実用上の問題がある道具を使わずに、手で汲んだ方が現実的です。そして、場面に応じてより適切な道具を選ぶと良いでしょう。

説明の誤り

より致命的な誤りは、Fira Code が README に掲げた説明にあります。

Fira Code: monospaced font with programming ligatures

Problem

Programmers use a lot of symbols, often encoded with several characters. For the human brain, sequences like ->, <= or := are single logical tokens, even if they take two or three characters on the screen. Your eye spends a non-zero amount of energy to scan, parse and join multiple characters into a single logical one. Ideally, all programming languages should be designed with full-fledged Unicode symbols for operators, but that’s not the case yet.

Solution

Fira Code is an extension of the Fira Mono font containing a set of ligatures for common programming multi-character combinations. This is just a font rendering feature: underlying code remains ASCII-compatible. This helps to read and understand code faster. For some frequent sequences like .. or //, ligatures allow us to correct spacing.

意訳すると、このような具合に。

Fira Code: プログラム用リガチャの等幅フォント

問題点

プログラマは、しばしば複数の文字で構成される、さくさんの記号を使います。画面上で2~3文字を占めていても、人類の脳にとっては ->, <=, := といったシーケンスは一つの論理的な塊です。あなたは、これらを一つの論理的な塊と見なすのに、少なからず労力を費やしています。理想的には、全てのプログラム言語は、演算子に Unicode 記号を本格的に用いるよう設計されるべきです。しかし、未だそうではありません

解決策

Fira Codeは、一般的なプログラム言語用のリガチャを含む Fira Mono の拡張です。これは単なる文字描画の機能によるもので、根底のコードはアスキー互換のままです。これはソースコードの より速い読解 に役立ちます。リガチャによって、..// といった良くあるシーケンスの間隔を修正できます。

何かの間違いではないか、と不安になりますが Google 翻訳とほぼ同じで、2014年11月の Initial Commit からほぼ変わっていません。

「あなた」がプログラマなら

プログラマにとって ->, <=, := といった演算子は、if, for, function などが「一つの論理的な塊」であることと同様の事です。

文字や言語という文明的な記号体系の中で、一つの論理的な塊一つの記号 でないことは、そもそも解消されるべき題材になりません。

また、記号を多用することが必ずしも可読性の向上にならない事は、多くの事例(古くは APLALGOL など)を通じて知られています。

Unicode 全盛と言える現在でも、一般的なプログラム言語の設計や、新しい機能や構文の導入に、アスキーコードの制約の中で挑むことが基本なのは、疑う余地が無いでしょう。

理想と現実の差は、未だ という時間の問題ではありません。

何度原文を読み直しても、道理に合いません。

「プログラマ」が人外で、「あなた」がプログラマ以外なら

前提を変えると、意味合いが変わってきます。

  • プログラマがプログラミングで 書く 演算子は、プログラマ以外の人類が 読む には、少なからず 負担となる。
  • 従って、理想的には、全てのプログラム言語とプログラマは、演算子に Unicode 記号を用いるべきだが、未だ そうではない。
  • よって、リガチャで、プログラマ以外の人類が より速く読解 できるようにした。

前提も中段も無茶苦茶ですが、動機としての筋道はそれなりに立ちます。

当然、プログラマがプログラミングに使うために開発されたフォントではない、という事になります。

プログラマの要求に応えられる仕組みでない手段にも、合点がいきます。

本当に、プログラマ以外の人類が より速く読解 できたら、良いですね。

もしかすると

よりシンプルな経緯を想像すると、

  • デザイン対象の一部のプログラム言語での演算子が 主観的に気に入らなかった から、自分好みに 見える ようにしたよ!
  • リガチャの表現の方が 主観的に気持ちいい から、ソースコードの読解が速まる 気がする よ!

という話を客観的に表現しようと頑張った結果の誤謬のようにも見えます。

逆説的ですが、気に入らなかったから対処した、という可能性は、強い動機として十分に成立します。

志を共にする人同士で使う位置付けなら、多少難のある話も「~と信じている」と信条の話で済みます。

……実際の動機に興味はないので、それより、書かれた文章通りの解釈で間違いがない説明にして欲しいですね。

おわりに

上位層の内容によって、下位層が振る舞いを変えるリガチャは、上位層に対して中立で汎用的な道具にはなりません。Microsoft が Windows Terminal 用の新フォント Cascadia Code を VC++ と合うようにデザインして利用場面のカバー率を上げても、手段の誤りによる乱暴で粗雑な性質は変わらないのです。

日本語環境では Windows Terminal でも MS ゴシック のビットマップ表示が続くのかもしれませんが、Cascadia Code がどのように仕上がるのか注目したいと思います。

好きな人が気に入って使うのは、自由というか自己責任なのですが、プログラマを人間扱いすると意味が通らなくなる説明文には、くれぐれもご用心を!

コメント

タイトルとURLをコピーしました