09.Fortressという言語

 Fortressという、並列処理言語は、2007年の私的な大トピックの一つである。

 並列処理言語ということで、なんとなく情報網にひっかかったというだけだが、「スレッドサイズを意識しないで並列処理を記述できる、新たなコンピュータ言語(あるいは概念)が必要」の予言に極めて近い概念を持つ言語なので個人的に気に入ってしまった。

 並列処理は足を突っ込むとわかるが、話は単純ではない事が多く、プログラム構造を整理する意味で概念的なものを覚える必要がある事が多い。

 ところが、Fortressは並列処理言語なのに、非常にわかりやすい構造をしている。最新の並列処理言語事情を見てみる事にしよう。


2007/12/29
●Fortressという言語

 Fortress言語は、2007年1月にSun MicrosystemsのGuy Steel氏ひきいる研究チームから発表されたばかりの、真新しいコンピュータ言語である。


 現時点(公開された2007年1月時点)の仕様書は1.0βで、まだ変更される可能性がある。3年後の2010年で一区切りつけたいという意向だそうで、現在、Sun Microsystems主導でオープンソースで開発が進んでいる。本気で参加したければ、本家のサイトを見てみるものいいだろう。(もちろん、全部英文だが。)

 調べてみると、米国防総省の肝いりで作られたHPC(high-performance computing)用並列処理言語の御三家(X10(IBM)、Chapel(Cray)、Fortress(Sun))の一つという事らしく、Fortressは最も遅く発表された言語だそうだ。

 また、Fortressは、FORTRANの持つ "永い" 歴史を受け継ぐ後継言語として開発されているが、ソース互換はない。どちらかと言うとFORTRANという言語名(Formula Transrator:直訳すると数式変換処理のような意味)をより現代的に解釈した "真のFORTRAN" と言えるかもしれない。(どうしてそう言えるかは、後述する。)

 なお、ここでMicrosoftの名前が出ていないが、もちろん蚊帳の外である。Microsoftは並列処理言語としてScript#という言語を作るつもりでいたらしいが、2007年の中旬時点でどうやらお蔵入りしたらしい。


2007/12/30
●今はインタープリタ

 現状のFortressはインタープリタ形式なので、メモ帳でプログラムを書いて、Fortreインタープリタにそのプログラムソースを指定すると解釈・実行してくれるという処理系である。

 並列処理言語の癖にインタープリタ形式というのが今ひとつ盛り上がりに欠ける所だが、ここ数年の期間の開発内容は、言語仕様の調整が主な役目になるため、当面、速度は期待できない。ただ、うまく開発が進めばそのうちネイティブコンパイラも登場することだろう。


2007/12/30
●Fortressでhello world

ではさっそくソースプログラムの例を見るとしよう。
これで本当に動くのかは保証の限りでないが、こんな感じである。

component testprogram
    import Executable
    run():()=print("hello world")
end

まず、大枠である。
component コンポーネント名
  :
end

component は、C#で言うところのnamespaceみたいな意味らしい。
COBOLで言えばIDENTIFICATION DIVISION に相当する部分だろうか。
よくわからないので、とりあえず無視。<ぉぃ
次。
    import Executable

これは、APIを取り込む指定である。Executableという名前のAPIを取り込むという意味である。
このAPIは、WindowsのAPIとは全然違うもので、コンポーネント間の定義内容らしい。WindowsAPIと言うときのAPIはApplication Interfaceという意味だが、FortressでのAPIが何の意味なのかよくわからない。

C言語の#includeマクロに近い役目かもしれないと思っているが、やっぱりよくわからない。
そういう訳で、これも無視。


という事で、プログラムの実質的な中身は
    run():()=print("hello world")

これ1行だけになる。
run()…と書いてあるので、形式的には関数を定義する指定となる。その一方で、runというのは特別な関数名になっているらしく、プログラムが実行されたとき、runという名前の関数を実行する事になっている。これは、C言語のmain関数と同じ役目になる。


関数定義の一般形は
関数名(引数1,引数2,…):戻り値の型名 = 

で、「run():()」は、関数名がrunで、引数がなく、戻り値として何も返さないという意味だ。
 引数を持たない関数なので、run()、括弧の中身は書かない。戻り値がないので戻り値の型名として:()を書く。もし整数型で返す場合は「run():ZZ32」(ZZ32は、32ビット整数型の意味)と書く。

で、run():() という格好になる訳だ。

 イコールの後ろにある「print("hello world")」は、説明不要だろう。標準出力に「hello world」という文字列を出力せよという関数だ。

 runという関数と、その中身である print() をイコールで結んでいるという形だ。


 話はガラっと変わるが、数学で数式を解く際に
…式を変形すると、
4(x+1)2+2(x+1)+x+1=0
となる。
ここで、f(x)=x+1 と置くと
と言ったりする。
run():() = print("hello world")
つまり、「関数名()=数式の本体」という体裁(書き方)は、全くもってそのノリから来ている。この辺がFORTRANよりも、より数式らしくなっている所だ。


2007/12/30
●もうちょっとだけ掘り下げる

 …プログラムが短かすぎて、かえってわかりにくいイメージがあるので、もう少し複雑にしてみる。

 これは、標準出力に「hello world」と出力した後に、0〜9の数字を出力するプログラムである。
 run():()=do 〜 end となって、do 〜 endの中に命令が並んでいる事に着目して欲しい。
component testprogram
  import Executable
  run():()=do
    print("hello world")
    for i←0#10 do
      print i
    end
  end
end

 Fortressでは、やりたいことを複数並べるには、式全体を do〜end で囲って、その間に必要な命令を並べる。

 要するに、Fortressという言語は、
  run():()=do
    あの命令
    この命令
    その命令
    :
  end

のように書けば、その通りに順番に実行される。
 最初のプログラムも
component testprogram
    import Executable
    run():()=do
      print("hello world")
    end
end

と書けば少しはわかりやすかったかもしれない。
 ともかく、基本はたったそれだけであり、別に難しくも何ともない言語だ。

 また、見た目だけで言えるのは、C言語のように特殊記号をやたらと使っている記号的な印象はないし、登場する単語も他の言語で比較的良く見る平易なものばかりである。難しそうだという印象はないと思う。

 見た目のイメージとしてはRubyやPascalに近いかもしれない。Pascalと大きく違うのは、文の区切り(C言語系では文末)に使われるセミコロン(;)がない。この辺がFORTRANの1行1文の文化を引き継いでいる所であり、ALGOL系言語(PascalやC言語)とは違う点だ。

 1行1文で困るのは「1行で書ききれないほど長い式を、わかりやすく複数行に分けられない」という事だ。
 Fortressでは、そういった場合は式を完結しない状態で改行すれば良い。
 z=a+b+c+
    d+e+f

と書けば、1行目の最後の+の足す相手がないため、次の行もひっくるめて評価されるという仕組みだ。
 ただし、もちろん、
 z=a+b+c
    +d+e+f

と書くと意味が変わる。上の式は上の式で完結してしまい、下の式は下の式で完結してしまって、結果が変わってしまう事になる。

 余談だが、Fortressは、当初Pythonのようにインデントによってブロック構造を取っていたらしいが、ユーザー(=学者:今時FORTRANを使う人は学者ぐらいしかいない)が難色を示したため、do-endでブロック構造を取る形式になったそうである。


2007/12/30
●Fortressの並列処理

 お待ちかねの、Fortressでの並列処理の記述方法である。

 最近のパソコンですらデュアルコアであり、現在のスパコンはほぼ例外なくマルチプロセッサである。新しいコンピュータ言語であるなら、複数のプロセッサを簡単にフル活用できる仕組みは必須と言える。

 さて、Fortressで並列処理はどのように記述されるかかと言うと、実に単純である。先ほど出てきた
  (* 0から9までの数字が「順不同で」表示される *)
  for i←0#10 do
    print i
  end

が、それである。(なお、Fortressでコメントは (*  *) で囲む。)
 これは0から始まる10個の数字について、同時に「その数字を出力しなさい」という処理を実行する事になる。だから、出てくる数字の順番は毎回バラバラになる。(0〜9を出力すると書いたが、順番に出力されるとは書いていない。)

 なんとなく "ループ" っぽい繰り返し処理に見えるが、実際は繰り返される事なく、原理的には10個のスレッドが生成されて並列で実行される。Fortressの持つ並列処理の仕組みは、たったそれだけである。
 言っちゃ何だが、たったこれだけの仕組みで「並列処理言語」なのである。

 この馬鹿馬鹿しく単純な並列処理しかないというのは朗報でもある。

 そもそも並列処理は、手段であって目的ではない。
 ハードウェア構成を気にして、「どのように処理を分割すればマルチプロセッサで効率良く計算されるか?」などという事は、本来アプリケーションプログラマが考える事ではない。(異論はあるだろうが、あえて断言する。)
 そして、FortressにはCPUトポロジに対応した細かいスレッド制御の文法は(現時点では)存在しない。
 コンピュータのプロセッサ構成やその数を気にせず、簡単に並列処理を記述できてしまう。また、ターゲットコンピュータがマルチコアだろうがグリッドシステムだろうがソースコードはその影響を受けない。このスケーラビリティの広さも特筆に値する。

 ただし、現実問題として並列で順不同に実行されると困る場合もある。Fortressでは仕様上、特に指定しない限りforループ部分は並列・順不同となるが、指定すれば、順番にもできる。
 順番に処理したい場合は
  (* 0から9までの数字が「順番に」表示される *)
  for i←seq(0#10) do
    print i
  end
 となる。
 並列処理上、ループ内を排他制御する必要のある場合は
  (* ループ内を排他制御 *)
  for i←0#10 atomic do
    sum := sum + i
  end
 となる。データの更新は、並列で行うと予想外の動きを起こしやすいパターンの一つであり、一連の処理を排他ブロックする必要がある。その場合はatomic指定を入れる。

 並列処理を行うと、いつどこで何の変数が書き換わるか予測できなくなるし、実行プログラム側からすれば常に競合が起きないように管理しなければならないという負担が生じる。デュアルコアぐらいなら並列処理にしない方がマシなケースも多い。

 そこで、Fortressには並列処理時の変数管理を容易にするため、値を設定する方法として2つの方法が用意されている。
  n = 1
 こちらは、nに1を固定的に割り当てる指定である。変数nが宣言されてから値を設定できるのは1度だけで、その後値を書き換える事ができなくなる。これは、並列ループ内からnを参照する場合、値が書き換わらない事が保証されているため、内部的に排他制御を行わなくて済むし、並列処理の性能も高くなる。

  n := 1
 一方、このスタイルは、nに1を代入する。従来の他の言語と同じで、いつでも自由に変更可能である。しかし、並列処理時には常に値が書き換わる可能性がある事を意味する。内部的に排他制御や同期作業が行われて効率が落ちるし、プログラミング上も不意の値変更の可能性がある事を念頭に置いておく必要が出てくる。

 Fortressの並列処理は馬鹿馬鹿しく単純だと書いたが、この辺は並列処理言語っぽい気の配り方をしないといけない。


2007/12/30
●Fortressのもう一つの特徴

 さて、もう一つの特徴だが、「Fortressは、式をそのまま数式のように書く。」という事だ。

 たとえば、nを4倍したものをyに設定したかったら
component testprogram
    import Executable
    run():()=do
        n = 3
        k = 4 n 
        (* ↑「k = 4 * n」とは書いていない。 *)
        print k
    end
end

である。

 三角関数を書きたければ、数式の通り、そのまま
 x = r cos θ sin α

と書いて良い。標準のキャラクタセット(使える文字)がUnicodeなので、乗算記号は * ではなく × だし、変数としてθやαといったギリシャ文字を使っても良い。(関係ないが、a' (エーダッシュ)のような変数も定義可能である)
 Unicodeなので日本語版でなくても日本語を使えるし、ハングル文字(韓国語)もOKだ。
 sinやcosといった引数を1つしか取らない関数は単項演算子のようにも使えるため、他のコンピュータ言語のように「関数には必ず括弧が必要」という事はない。ついでなので、「print k」のように括弧を外して書いてみたが、このように書いてもOKである。

 また、現在のインタープリタには実装されていないらしいが、変数に単位を付ける事もできる。
x = 3 minute_
y = 5 second_
z = x + y

(単位名はローマン書体にする都合、最後にアンダーバーが付くらしい。仕様書を詳しく読んでいないので誤解しているかもしれないことは注記しておく。)
これによって z に設定されるのは 3分5秒 である。(おそらくSI単位系で丸められるので、185秒という値になるだろう。)

 インチとセンチ、フィートとメートルを混ぜて書いても良いし、角度も度とラジアンを混ぜて書く事もできる。


2007/12/30
●真のFORTRANを狙うFortress

 Fortressがこのような書き方を許容する理由は、FORTRANの後継言語という位置づけで作られているためだ。Fortressの作者である Guy Steele氏は、現在のFORTRANが本当の意味でのFORTRAN――Formula Transrator(数式変換)ではない所に着目した。

 プログラミングと言えばアセンブラでしかできなかった時代において
 X = Y + 1 
のように数式のようにプログラムを書けるFORTRANという言語は、当時極めて画期的であった。
 その一方で、FORTRANは会計用コンピュータで産声を上げた事による悲劇もある。乗算記号である×ですら文字セット(EBCDICやASCIIコード)に存在しないという事だった。APLのようにプログラムを記述するための専用キャラクタセットも用意できるほどハードウェア資源に恵まれない時代である。

 仕方なく * を乗算記号として代用したのだが、その伝統によって、今でもほとんどの言語ではyにxの4倍を代入したければ、
y = x * 4
のように書いているし、プログラマのほとんどはそのように書く事が常識だとすら思っている。

 現在のコンピュータは、Unicodeという膨大なキャラクタセットが扱え、その中には豊富な数学記号が活字として定義されている。Unicodeで扱えば「*の記号を乗算とみなす」というような前時代的な伝統を重んじる必要は全くない。

 また、ディスプレイもキャラクタディスプレイ(文字しか表示できないディスプレイ)からグラフィックディスプレイ(図を描けるディスプレイ)に変わった。グラフィックディスプレイなら、フォントも文字の大きさも自由に選択できる。ソースコードをレンダリングすることも可能なので、より数学記号らしく描く事も可能である。
 Fortressは「数式を見たまま書けば、そのままプログラムになる」まさに「真の」FORTRANを実現しようとしている。


2007/12/30
●ソースコードのレンダリング表記

 Fortressは式を数式のまま書けるとは言ったものの、分数は二階建てである。ソースコードは、コンパイラが読み取る都合上、1行で完結するように書かなければならない。

 Fortressのソースコードは、おおざっぱに言うと、ASCII(Unicode)表記とレンダリング表記の2種類を備えている。ASCII表記で x=1/y と書いておき、それをレンダリング表記にすると分数表記の二階建てで表示されるという仕組みだ。



 どちらかと言うと、こちらの方が目立つ特徴と言えるだろう。
 (Σの書き方が違うんじゃないかというツッコミは当然あるが、これは数式ではなくソースコードである。)


2007/12/30
●数学らしくプログラムを書くFortressの余談

 余談を2つほど。Fortressでは
  n = n + 1
 と書くとコンパイルエラーになる。
 他のコンピュータ言語ではきわめて当たり前の書き方なのだが、コンピュータ言語を学んだ時に違和感を覚えた人も少なくないだろう。
 Fortressでは当然「誤り」になる。
 もっとも、並列処理上、固定的に割り当てる指定が = であり、同じ変数を使って再定義しようとしているからエラーになるだけの話だが、数学的に正しくないからエラーになっているようにも思えておもしろい。
 なお、nに1を加えた値をふたたびnに代入したければ
  n := n + 1
と書く。これは前にも触れた。

 もう一つ。if文の条件式の書き方である。Fortressでは「xが0以上10以下であれば」という条件を書きたければ
if 0 ≦ x ≦ 10 then
    k = 1
else
    k = 0
end
と書く。この書き方は数学的に当たり前なのだが、他のコンピュータ言語には見られない書き方である。

 たとえば、VisualBasicならこう書かなければならない。
If 0 <= x And x <= 10 Then
    k = 1
Else
    k = 0
End If
 なぜこう書かなければならないかと言うと、不等号の演算子が2項演算子でしか定義されていないからである。VisualBasicでは、
If 0 <= x <= 10 Then
    k = 1
Else
    k = 0
End If
 と書いても文法的にエラーにはならないが、予想外の動きをする。なぜなら、こう解釈されるからだ。
If ((0 <= x) <= 10) Then

もしxが20であれば、まず「0 <= x」が評価され、結果、真を示す-1という値になる。xがマイナスの値などで成立しなかったら0である。
 続いて10との比較だが、「-1 <= 10」あるいは「0 <= 10」となり、xの値によらず常に真になってしまう。
 「xが0以上10以下であれば」というつもりで書いたのに、VisualBasicでは文法上「0 <= x <= 10」という条件式は常に成り立ってしまうのだ。
 

 一方、Fortressでは≦のような演算子は多項演算子として定義されているらしく、演算子を数珠つなぎしても見たまま評価される。
 たとえば、
if a = b = c = d = 0 then
    k = 1
else
    k = 0
end
 ならば、a,b,c,dすべて0の場合に限り真である。

 改めて考えると、従来の言語で、なぜ
if 0 <= x <= 10 then

と書く事を許されていなかったのか不思議で仕方ない。

 コンピュータ言語は論理構造しか持たない人工言語ではあるが、数学の数式もまた曖昧な表記を許さない論理言語である。

 コンピュータ言語で複合的な条件判定をする場合「不等式とBool代数を混ぜた書き方をしないといけない」というのは、数学の表記が曖昧だという事ではなく、単にコンピュータ言語側の都合でしかなかったのである。

 この辺の数式のさばき方を見ると、さすがCommon LISPを整理した人だと関心する。


2007/12/30
●特徴の残り1つ

 もう一つ特徴があって、「高い拡張性」というのがあるらしいが、よくわからないので説明はパスする。

 「なんでも拡張できる」という事は、エラーの内容が具体的に指摘できなくなってわかりにくくなるなどの弊害を生みやすい。たとえば、fという関数のつもりでFという名前にしてしまったとき、コンパイルエラーにならず(おそらくfという名前の外部ライブラリが用意してある"はず"だという事でコンパイルを通す)、リンク時に「ラベル'F'のエントリーが見つかりません」というエラーになったりする。「関数'F'は定義されていません」という親切なメッセージにはならない。
 この辺はFortressへの批判の中でも目立つ部分ではあるし、GuySteele氏もそれもよくわかっているようだ。

 詳しくは検索して欲しいと言いたい所だが、現実問題、米国のSunMicrosystemsのサイトに公開されている情報を見る他ない。

 日本語の解説本もあるが、どんな言語かを知るきっかけになる程度であり、正直言って参考にならない。部分的に仕様に対する誤解も散見される。そもそも現在の最新仕様がβ版であり、動かせる処理系には定義されている仕様が完全実装されていないため、掘り下げて解説するにも限度があるという理由もあるだろう。


2008/03/16

[戻る]