糖衣構文

プログラミング言語において読み書きのしやすさのために導入される書き方

糖衣構文(とういこうぶん、: syntactic sugar あるいは syntax sugar)は、プログラミング言語において、読み書きのしやすさのために導入される書き方であり、複雑でわかりにくい書き方と全く同じ意味になるものを、よりシンプルでわかりやすい書き方で書くことができるもののことである。

構文上の書き換えとして定義できるものであるとも言える[* 1]

名称について

編集

syntactic sugar の直訳に近い構文糖(こうぶんとう)という用語も使われる。また外来語としてそのままシンタックス・シュガーとも呼ばれる。糖衣構文あるいは構文糖衣とするのは少々意訳的だがよく使われている[* 2]

語源は「取り扱いやすい」を意味する sweet の第一義が「(砂糖のように)甘い」であることから[1][2]

解説

編集

糖衣構文はプログラムの意味としては同じものを、よりわかりやすい構文で書けるものである。ジャーゴンファイルsyntactic sugar の項[3]では、アラン・パリスの「構文糖はセミコロンのガンをひきおこす」という言を引用[4]している(ジャーゴンファイルのこの記述は、そのひとつ前の項目である「構文塩」(syntactic salt) の項[5]にある「構文塩は不健康にプログラマの血圧を上げる」という記述と対応していて、colon cancer(結腸癌)とセミコロンを掛けてもいる。

逆に糖衣構文から元の構文に戻すことを desugar や脱糖という。糖衣構文の役割を考えると無駄な作業に思えるが、実際にどうなっているかを把握するためであるとか、場合によっては短く書けたり、また一般に柔軟性(自由度)は元の書き方のほうがすぐれる。脱糖したコードを把握しておくことが重要な一例として、Haskellモナドが挙げられる。Haskellには、(モナドをなしている対象を)命令型言語のコードに似せたスタイルで書ける、do式(do expressions[6])という糖衣構文がある。しかし、自分でその対象自身を書く場合は、do式を脱糖したコードに現れる演算子(具体的には >>= という演算子[7])を、その対象について定義しなければならないからである。

糖衣構文の例

編集

以下ではプログラミング言語ごとに代表的な糖衣構文の一部を挙げる。糖衣構文の多い言語は自由度が高く語彙が豊富であるとも言えるが、たとえ同じ意味のプログラム内容であってもプログラマによってまったく異なるソースコードを書くこともできてしまう。糖衣構文の少ない言語では、自由度が低い代わりに覚えるべき文法が少なくて済み、誰が記述しても似たようなソースコードになりやすい。

C言語

編集

C言語の糖衣構文のうち、特筆性の高いものはポインタに関連する構文である。また、制御構造に関連する糖衣構文はC++JavaC#といったC系の後発言語にもそのまま引き継がれている。

ポインタへの配列風アクセス

編集

C言語では、例えばオフセット付きでポインタをデリファレンスするような式 *(p + ofs) を、配列へのアクセス風に p[ofs] とも書ける。この糖衣構文は、見た目の単純さだけではなく、括弧の手前に書かれるものが先頭アドレスで括弧の中がオフセットであるというように、コードの意味をより明確で分かりやすくもしている。また、C言語の関数には配列そのものを引数として渡すことはできず、代わりに配列先頭要素へのポインタを渡すことで代替するが、例えば関数宣言void func(double *x, size_t count)は、void func(double x[], size_t count)と書くこともできる。後者は引数として配列を渡すことを想定していることがより明確になる。なお、配列変数のシンボルは、式の中では配列先頭要素へのポインタと解釈されるため、例えば要素数10の固定長配列double a[10]の先頭要素へのポインタを関数funcの実引数として渡す場合に、func(&a[0], 10)と書けるだけでなく、func(a, 10)と書くこともできる。

一方で、これらの糖衣構文は、配列とポインタはデータ構造としても文法要素としても異なるものである(例えば extern int *a; という宣言と extern int a[]; という宣言とは、それぞれ違うものである)という事実を見失わせ、あらゆる場所で配列とポインタは可換であるというように誤った解釈や混同をしてしまう要因にもなっている。

なお、前述の p[ofs]ofs[p] のように書くこともできる。例えば文字列リテラルにインデックスでアクセスする、整数値から十六進数表現を得るための、次のようなコード "0123456789abcdef"[n] の代わりに n["0123456789abcdef"] と書くこともできる。後者も構文的に問題はなく、全く同じ意味となる。

ポインタを介したメンバ選択演算子

編集

C言語では、構造体へのポインタからメンバーを参照する場合、アロー(矢印)演算子->を使うことができる。

p->member

ここで、p構造体へのポインタ、member は構造体の要素名である。 ポインタをデリファレンスする*演算子と、構造体の値に対しメンバーを選択する.演算子を組み合わせて、次のように等価な式を書くことができる(が、倍の文字数と、カッコを必要とし、意味を持つ部分が*.の2箇所に分散してしまっている)。

(*p).member

なおC++では、演算子のオーバーロードにより、これらが等価ではない場合もありうる(基本的には等価になるようにオーバーロードされるべきではあるが)。

分岐

編集

if文switch文条件演算子三項演算子)はいずれも分岐を表現できるが、それぞれ制約が異なり、異なる長所・短所がある。詳細は各項目を参照のこと。

ループ

編集

for文while文do-while文はいずれもループ構造を表現するための構文であり、本質的には同じだが、実行するループの内容や特性に応じて書きやすさが異なる。

Erlang

編集

Erlang でのレコード型データは、実際にはタプル型データに対する糖衣構文であるが、要素を取り出したり、パターンマッチするのに糖衣構文が使われる。

%% レコードの定義(コンパイラー・マクロ)
-record(book, {title, author, lang, isbn}).

%% タプル {book, "Learn You Some Erlang for Greate Good!", "Fred Herbert", en, "978-1-59327-435-1"} になる。
B = #book{title="Learn You Some Erlang for Great Good!", author="Fred Herbert", lang=en, isbn="978-1-59327-435-1"}.

%% タプルの要素取り出し element(2, B). の糖衣構文
B#book.author.

%% タプルの要素置き換え setelement(2, B, "F. Herbert"). の糖衣構文
B#book{author="F. Herbert"}.

%% レコードのパターンマッチの部分はタプルのパターンマッチ printCover({Title, Author, _, _}) の糖衣構文
printCover(#book{title=Title, author=Author})
  -> io:format("~p by ~s~n", [Title, Author]).

Javaの配列の宣言と初期化の記法

String[] strs = new String[3];
strs[0] = "a";
strs[1] = "b";
strs[2] = "c";

String[] strs = { "a", "b", "c" };

と書ける[8]String以外のオブジェクトでも可能である。 後者は「あらかじめ決まったいくつかの文字列から文字列の配列を作りたい」という記述者の思考を、より良く反映している。

C#は言語仕様の更新が比較的頻繁に行なわれており、その際に糖衣構文も数多く追加されている。

.NET Framework 3.5では、コレクション等に対して遅延実行される複雑な反復処理を簡潔に記述できるLINQ (Language INtegrated Query) が追加された。これに伴い、C# 3.0以降にはSQL構文風のクエリ文字列で表現することができるLINQクエリ構文が用意されているが、これは内部的にはラムダ式をパラメータに取るメソッドチェーンによる等価の反復処理表現(LINQメソッド構文)に置き換えられる。

using System.Linq; // どちらの構文でも必要。

var src = new char[] { 't', 'e', 's', 't' };

// 以下のコードはそれぞれ等価であり、文字't'のみを抽出した新たなシーケンス(IEnumerable<char>型)を返す。
var p = from c in src where c == 't' select c;
var q = src.Where(c => c == 't').Select(c => c);

インデクサ

編集

C#ではオブジェクト内部のコレクション呼び出し等に使うgetter/setterメソッド呼び出しを配列同様の添字表現に置き換えることのできるインデクサが糖衣構文として用意されている。

public Class UserDefinedClass
{
    private Dictionary<string, object> collection;
    
    // インデクサによる内部コレクションへのアクセス(初期化・nullチェック等は省略)
    public object this[string key]
    {
        get { return collection[key]; }
        set { collection[key] = value; }
    }
}

実質的にはgetter/setterメソッドの実装と等価であるが、ListやDictionaryのようなコレクションクラスへのアクセスをより直接的に記述できる。

ここでは、ML系の言語のDerived Formsについて説明する。[9]

t_1 ; t_2 は、手続き型言語においても見受けられるように、いわゆる命令文の列であるが、型付きラムダ計算において、 (λx:Unit : t_2) t_1 と表すことができる。(ここで、Unit はユニット型 である。)

Perlの条件文の記法

if ($boolean) {
  print "Syntax sugar\n";
}

print "Syntax sugar\n" if $boolean;

と書ける。

Perlの開発者ラリー・ウォールによればこれは糖衣構文であるが、プログラマの中にはこれを読みにくいと感じる者も多い。それは、「読み書きのしやすさ」が主観に基づくためである。

Python

編集
def decorater(func):
    def wrapper(*args, **kwargs):
        print("Function called")
        result = func(*args, **kwargs)
        print("Function ended")
        return result
    return wrapper

def pure_func(a, b):
    return a + b

covered_func = decorater(pure_func)

@decorater
def decorated_func(a, b):
    return a + b

上記コードにおけるcovered_func関数とdecorated_func関数は動作的には等価である。しかし、@で始まるデコレータにより、decorated_funcは比較的わかりやすく実装されている。 また

my_list = [1, 2, 3]
my_list[0] = 2
print(my_list[0])

my_list = [1, 2, 3]
my_list.__setitem__(0, 2)
print(my_list.__getitem__(0))

の糖衣構文である。

脚注

編集

注釈

編集
  1. ^ これを「マクロで定義できるもの」とするのはあまり正確ではない。C言語をはじめ、多くの言語のマクロはテキストレベルであり、構文木を自在に編集することは困難であるためである。Lispであれば「糖衣構文はマクロで定義できる」と言える。
  2. ^ 糖衣は sugarcoating である。

出典

編集
  1. ^ syntactic sugarの意味・用例|英辞郎 on the WEB:アルク
  2. ^ syntax sugarの意味・用例|英辞郎 on the WEB:アルク
  3. ^ The Jargon File 4.4.8, syntactic sugar
  4. ^ 1982年にSIGPLANに掲載された en:Epigrams on Programming からの引用である。
  5. ^ The Jargon File 4.4.8, syntactic salt
  6. ^ Haskell 2010 Language Report, 3.14 Do Expressions
  7. ^ http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:-62--62--61-
  8. ^ Java Language Specification Third Edition, 10.6
  9. ^ Pierce, Benjamin C.. Types and Programming Languages. ISBN 0-262-16209-1