Fortress触り始め

Fortressは、Schemeを作った一人とかで色々有名なGuy Steeleらが作ってる言語。JavaがCにしたようなことを、Fortranにもやろう、という話で、何故か結構期待してたり(なんか面白そう)。

前にDLしてこのあたりを参考に、一応動くようにしたところで止まってたんやが、そろそろ触ってみることにした。とりあえずピボット処理なしの手抜きガウスの消去法を実装してみる。
で、根拠もなく何となくできるやろ、とか思ってたんやが結構わからん…。頑張って公式の言語仕様書を読んだり、アーカイブに入ってるdemoを見たりして、なんとか必要な処理を理解できたような気がする。以下メモ。

デモの中身を見てみると、不可解な記法が使ってあって一瞬腰が引けるが、どうも数式をASCIIで書き下してる関係でこういうことになってるらしい。例えば型の名前なんかやと、32bitの整数を表わしたいときは、整数集合が Z なんで ZZ32 と書く。実数なら RR64 とか。なんか黒板で書く太字表現(線引いて太字ってやつ)は字を2つ並べて表わすとか、色々仕様書に書いてある*1

変数の宣言は、var ってキーワードを付けんとstaticな宣言になるらしい。

 var x:ZZ32 = 3

とか。配列なら

 var a:RR64[2,2] = [1 2; 3 4]

とか

 var a:RR64[2,2] = [1 2
                    3 4] 

なんて芸当もできるらしい。更に

 var a:RR64[2,2,2] = [1 2
                      3 4;;5 6
                           7 8;;9 0
                                1 2]

とか、もうわけわからんリテラルもあり。ちなみに定数変数(immutable variable)の初期化は = で、変数(mutable variable)の代入(assign)は := を使う?

関数(メソッドも同じ?)の宣言は、どっかのスクリプト言語っぽい。ちなみに関数は first-class object。

 add(a:ZZ32, b:ZZ32) = a + b

という感じ。複文にしたいときは do を使う。この場合返り値は最後の式(この場合は result)。

fact(n:ZZ32) = do
  var result:ZZ32 = 1
  for i <- 1#n do
    result *= i
  end
  result
end

可変長配列をどうやって関数への受け渡したらええかわからんかったが、static parameter なるものがあって、これでnat(Natural Number) とか int とか dim(Dimension) とか色々渡せるらしい*2。これで受渡したパラメータを使って引数の型情報を決定できるんで、なかなか便利っぽい。パラメータは、関数宣言時に引数の前に [\ と \] で囲んで、C言語の引数みたいに書くらしい。

test[\nat m\](arr:RR64[m]) = …

みたいな。日本語環境だとバックスラッシュが円マークに化けて美しくない。

あとfor文は基本的に並列実行らしい。

 for i <- 0#10 do println("" i "! = " fact(i)) end

とかすると0から10個の数字(つまり0から9まで)で、println(…) を並列実行することになる。ちなみにさっき定義したfactは壊れていて*3、0を食わせると無限ループに陥る*4。そこでこれを実行すると、

8! = 40320
9! = 362880
4! = 24
2! = 2
5! = 120
6! = 720
1! = 1
3! = 6
7! = 5040

という感じで、ちゃんと並列実行されていることがわかる。勿論待っても0!は出てこないので、強制終了するのみ。
で、シーケンシャルにforをまわすには、

  for i <- seq(0#10) do … end

というふうに seq(もしくはsequential)で 0#10 とかのgenerator を囲む。

で、以上を踏まえて目標のガウスの消去法を作ってみたのが以下。非効率的でも気にしない。

component Gauss
export Executable

mat_display[\nat m, nat n\](mat:RR64[m,n]) = do
  for i <- seq(0#m) do
    for j <- seq(0#n) do
      print( "  " mat[i,j] )
    end
    println("")
  end
end

gauss[\nat m\](mat:RR64[m, m+1]):RR64[m,m+1] = do
  var result:RR64[m,m+1] := mat
  var t:RR64 := 0.0
  var i':ZZ32 := 0

  for i <- seq(0#m) do
    t := result[i,i]
    for j <- 0#m+1 do result[i,j] /= t end
    if i < m-1 then 
      for ii <- i+1#(m-i-1) do
        t := result[ii,i]
        for j <- 0#m+1 do result[ii,j] -= result[i,j]*t end
      end
    end
  end

  for i <- seq(2#m-1) do
    i' := m-i
    for j <- i'+1#i-1 do
      result[i',m] -= result[i',j]*result[j,m]
      result[i',j] := 0.0
    end
  end

  result
end

run(args:String...) = do
  a:RR64[3,4] = [3.0 2.0  1.0 0.0
                 2.0 5.0 -4.0 0.0
                 4.0 1.0 -3.0 4.0]
  mat_display[\3, 4\](a)
  println("")
  mat_display[\3, 4\](gauss[\3\](a))
end

end

割となんとかなるもんだ。

使ってみた感想は、Fortranとはかなり違うなぁ(多分過去の遺産は使えない)と思う一方で、これは予想以上に面白い言語なんじゃないか、という予感がする。まだオブジェクトシステムは触ってへんけど、ちゃんとした処理系が出ればかなり実用になりそう。

*1:仕様書P48の末尾あたりのASCII Convertion

*2:仕様書P86のChapter11あたりに詳しく書いてある気がする。

*3:そもそもこんな書きかたはしない。普通は PRODUCT を使うんではないかと思う。あと、requires ってのを使って、関数に渡された値のチェックはできるらしい。

*4:現行バージョンだと、forを0個の数字で実行しようとすると無限ループになる。バグ?