XL (プログラミング言語)
XLはeXtensible Language(拡張可能言語)を表しており、コンセプトプログラミングに則り設計された、2016年時点で唯一のプログラミング言語である。
XLはプログラマによって再構成可能な構文と意味論を持つことを特徴としている。コンパイラプラグインを使って言語に新しい機能を追加できる。あるプラグインの基本セットは比較的標準的な命令型プログラミング言語を実装している。プログラマは独自のプラグインを書くことで、記号微分のように個々の応用に則した記法を実装し、組み込みの言語機能と同様に使用できる。
XLは4つの異なるレベルで定義されている:
XL0: 入力文字列を構文木に変換する方法を定義する。
XL1: C++に相当する機能を備えた基本言語を定義する。
XL2: 基本データ型と演算子を含む標準ライブラリを定義する。
XLR: XL0を基にしたXLの動的ランタイムを定義する。
XLにはプリミティブ型や予約語がない。整数や加算演算子といった、有用なデータ型と演算子は、すべて標準ライブラリ(XL2)で定義されている。XL1は異なる実行環境間で移植可能である。一方でXL2にはそのような保証はなく、もしCPUが浮動小数点数の乗算を実装していなければ、それに対応した演算子の定義は標準ライブラリから欠落しているかもしれず、また、浮動小数点数の乗算はコンパイル時エラーとなるかもしれない。
XLで書かれたHello Worldは次のようになる。
use XL.TEXT_IO
WriteLn "Hello World"
より大規模なプログラムにふさわしいスタイルで書かれた別のプログラムは次のようになる。
import IO = XL.TEXT_IO
IO.WriteLn "Hello World"
XLRでの階乗の再帰的な実装は次のようになる。
0! -> 1
N! -> N * (N-1)!
構文はXL0レベルで定義されている。コンパイラのXL0フェーズは、テキストの内部表現や演算子の優先順位といったプロパティが定義されている構文記述ファイルによって設定可能である。基本構文ファイルは、加算の+といった、一般的な数学の記法を、普段用いられる演算子の優先順位で定義している。
構文木は7種のノードから成る。4種の葉ノード(整数・実数・テキスト・シンボル)と、3種の内部ノード(中置・前置・ブロック)が存在する。
整数ノードは、2のような整数リテラルを表現する。#記号を、基数に10以外を指定するために使うことができる。(2進数の1001は2#1001。)アンダースコアを読みやすくするための区切りとして使うことができる。(例: 1_000_000)
実数ノードは2.5のような整数でない数を表現する。整数ノードと同様に、基数の記法や区切り記号を使うことができる。たとえば、16#F.FFF#E-10は正しい実数リテラルである。
テキストノードはテキストを表現する。普通はシングルクォートかダブルクォートで囲まれる。(例: "Hello", 'a')しかし、構文記述ファイルで定義することで、他の区切り記号を用いることや複数行のテキストを含めることも可能である。
シンボルノードは名前や演算子を表現する。名前はアルファベットから始まる英数字の並びである。(例: Hello)XL0は大文字・小文字を区別するが、XL1は大文字・小文字とアンダースコアを無視するため、JohnDoeとjohn_doeは同じ名前である。演算子は非英数字の並びである。(例: *, =/=)
中置ノードは中置シンボルに関係する2つのノードを表現する。(例: A+1, 2 and 3)とりわけ、行を区切るために、「改行」シンボルの中置ノードが使われる。
前置ノードは2つの連続したノードを表現する。(例: Write "Hello")これは前置記法だけではなく、後置記法にも用いられる。(例: 3!, Open?)
ブロックノードはグループ化シンボルに囲まれたノードを表現する。(例: (A), [Index])また、インデントは内部的にはブロックノードとして表現される。
デフォルトの構文記述ファイルでは、意味論に関わらず、次のものはXL0による正しい記述とされる。
A = B + "Hello"
これは、次のように構文解析される。
infix("=",
symbol("A"),
infix("+",
symbol("B"), text("Hello")))
XL1フェーズはXL0構文木に対しての一連の操作として定義されている。操作は様々なコンパイラプラグインとして提供され、それぞれ構文木の形に従って呼び出される。
あるプラグインによって、他のプラグインを楽に記述できるようにするためにtranslateとtranslationという特別な構造が提供されている。quote構造は構文木を生成する。これらの構造を用いて、ZeroRemovalという意味のないゼロ加算とゼロ乗算を計算するプラグインを、以下に示す。
translation ZeroRemoval
'X' + 0
return X
'X' * 0
return parse_tree(0)
プラグインはコマンドラインを使ってファイル全体に適用させることができる。また、以下のようにpragma表記を使って、ソースコード内だけで完結させることができる。
X := {Differentiate} d(sin(omega * T) * exp(-T/T0)) / dT
XL1フェーズはXLSemanticsに代表されるような、沢山のプラグインを持ち、それらはプログラミングでの主な概念であるサブルーチン、データ型、変数、宣言、定義に加えて、基本的な構造化プログラミングに使う条件分岐や繰り返しを提供する。
XL1の型システムは静的型付けだが、ジェネリックプログラミングの方式がAbaやC++などの言語とは異なる。配列やポインタなどは、C++ではプリミティブ型であるが、XLではライブラリで定義される。以下に一次元配列の定義を例示する。
generic [Item : type; Size : integer] type array
有効なジェネリック型(validated generic type)とは、その型がどのように使われるかを自身で表せる型であり、このような型にはジェネリック引数が必要ない。例として、不等号を含むときに型をorderedと宣言するものを示す。
// A type is ordered if it has a less-than relationship
generic type ordered if
A, B : ordered
Test : boolean := A < B
これによって、ordered自身がジェネリックとなるから、潜在的にジェネリックとなる関数を定義することが可能となる。
// Generic function for the minimum of one item
function Min(X : ordered) return ordered is
... compute Y of type ordered ...
return Y
さらに、arrayのようなパラメータを持つジェネリック型にも適用できる。任意の配列(array)の和を計算する関数を以下に示す。
function Sum(A : array) return array.Item is
for I in 0..array.Size-1 loop
result += A[I]
演算子は関数宣言のうちのwritten形式を使って定義することができる。以下に整数の足し算を定義するコードを示す。
function Add(X, Y: integer) return integer written X+Y
writtenを使った定義には三つ以上の引数を取ることができる。例として線型変換の行列を示す。
function Linear(A, B, C : matrix) return matrix written A+B*C
writtenは定数も扱える。これによってより特殊な演算子を定義できる。
function Equal(A, B : matrix) return boolean written A=B
function IsNull(A : matrix) return boolean written A=0
function IsUnity(A : matrix) return boolean written A=1
基本的な演算子はすべてこのメカニズムで実装される。式はwrittenを用いた関数呼び出しに集約される。よってこのメカニズムは関数オーバーロードというよりも、式削減(expressin reduction)に近い。
XLイテレータはジェネレータとイテレータの両方を扱える。
import IO = XL.UI.CONSOLE
iterator IntegerIterator (var out Counter : integer; Low, High : integer) written Counter in Low..High is
Counter := Low
while Counter <= High loop
yield
Counter += 1
// Note that I needs not be declared, because declared 'var out' in the iterator
// An implicit declaration of I as an integer is therefore made here
for I in 1..5 loop
IO.WriteLn "I=", I
XLは1992年頃から長い時間をかけて開発された。最初のバージョンはクリストフ・ド・ディネシャン (Christophe de Dinechin)が設計し、発表した。
XLのコンパイラは最初はC++で書かれていて、上に示すような機能のほとんどは正しく実装されていた。しかし、C++自身は拡張的でないため、プラグインを書くのは難しく、translateのような構造の実装は不可能であった。構文木はさらに複雑で、他のプログラミング言語でも扱えるようにするために非常に多くの木構造を実装する必要があった。Mokaは同様の仕組みを使ったJava-to-Javaの拡張可能なコンパイラである。
他プログラミング言語対応と複雑な構文木構造を諦めて、2003年にコンパイラの再開発が始まった。構文木は七つのタイプに大幅に簡略化された。2004年にXL自身で書かれたコンパイラが完成し、それ以降の開発はすべてXLで完結している。新たなコンパイラもXL1の実装がまだ不十分であるが、すでにいくつかの点でC++を上回っている。
XL1は多くのプログラミング言語から影響を受けている。
Adaは、例外処理、タスク処理などを含めた広範囲の部分に影響を与えた。
BASICの行番号や構造化プログラミングの考えは、プログラミング言語がいかに簡潔にできるかの指標を示した。
C言語はランタイムや機械語などの分野の基準であり、XLを動かすのに仮想マシンを用意する必要をなくした。
C++とStandard Template Libraryは、(Abaにはない)ジェネリック型の暗黙の具体化などのジェネリック型に関するサポートの素晴らしさを示した。
Fortranは数値計算の分野ではCやC++をしのぐパフォーマンスを持っており、最適化に有効な言語構造とは何かを示した。
Javaはライブラリのポータブルサポートの重要性を示した。Javaのコンテナは、ジェネリックプログラミングを使わない場合の限界も示した。Javaのコード体系からも影響を受けた。
Lispの拡張性は今日に至るまで重要視されている。Lispは最初にオブジェクト指向の考えを標準化した。ただしLispの歴史はさらに古い。
Prologは異なるプログラミングモデルの利用が、時として便利で生産的であることを示した。XLのプラグインはPrologスタイルで書くこともできる。
Visual Basicは構造木表現がビジュアル化された表現とは切り離して考えられることを示した。VBの編集を逐語的にする人は少ない。XLも直接的に構文木を扱うことで、同様の能力を持つ可能性をもっている。
XLRは動的言語だが、元々はXL Runtimeの略でXL1コンパイラのバックエンドとして存在した。XLRはXL0の文法をXL1と共有しているが、動作はむしろ関数言語に近い。逆にXL1は命令型言語に近い。XLRは実質的には"->"という一つのオペレータのみを持ち、その役割は書き換えである。左側の記述が右側の記述に変化させられる。
このメカニズムを使って、標準的な式を実装する。
if true then TrueBody else FalseBody -> TrueBody
if false then TrueBody else FalseBody -> FalseBody