安全ではないC言語スタイルのATSプログラミング

おそらく、ATS はコードを書き下すことが簡単なプログラミング言語ではないでしょう。 ATS は安全な低レイヤープログラミングをサポートするために多くの機能を提供しています。 プログラマがそれらの機能を学習して使いこなせるようになるまでに、 長い時間とある程度の努力を費やす必要があるかもしれません。 この章では、C言語スタイルで書かれた ATS コードをいくつか紹介します。 これは、ATS プログラミングにおける安全ではない機能の典型的な使い方で、 C言語コードを書き慣れたプログラマにとってなじみ深いプログラミングスタイルです。

安全でないC言語スタイルプログラミングが懸命である場合があります。 ときには単に実行できる実装を手に入れて、バグを見つけて修正するためにテストを信頼しようとするかもしれません。 ときには安全に関数を実装する ATS プログラミングの作法を、熟知していない場合もあるでしょう。 またそれは単に個人の要望であるかもしれません。 私自身しばしば安全ではないC言語スタイルの ATS プログラミングをすることがありますし、 ただ ATS でコードを書きたいのではなく高い生産性が欲しい人にとって、それが必要なスキルであることも理解できます。 安全ではないC言語スタイルの ATS プログラミングの具体例を見てみましょう。

2つの文字列を標準辞書式順序によって比較する関数を実装したくなったとします。 その関数に strcmp という名前を付けて、次のようなインターフェイスを与えましょう:

fun strcmp (str1: string, str2: string): int

2つの文字列 str1str2 が与えられると、 strcmp(str1, str2) は 1, -1, 0 のいずれかを返します。 それぞれ str1str2 と比較して、 より大きいか、より小さいか、等しいことを表わしています。 strcmp の実装は次のようになるでしょう:

staload UN = "prelude/SATS/unsafe.sats" (* ****** ****** *) implement strcmp (str1, str2) = let // fun loop (p1: ptr, p2: ptr): int = let // val c1 = $UN.ptr0_get<uchar> (p1) val c2 = $UN.ptr0_get<uchar> (p2) // in case+ 0 of | _ when c1 > c2 => 1 | _ when c1 < c2 => ~1 | _ (* c1 = c2 *) => ( if $UN.cast{int}(c1) = 0 then 0 else loop (ptr0_succ<uchar> (p1), ptr0_succ<uchar> (p2)) // end of [if] ) end (* end of [loop] *) // in loop (string2ptr(str1), string2ptr(str2)) end (* end of [strcmp] *)

C言語になじみのあるプログラマにとって、 上記の strcmp の実装は理解しやすいでしょう。 unsafe.sats には安全でない関数群が各種宣言されています。 型 T とポインタ p が与えられた時、 ptr0_get<T> (p) は p が指し示す位置に配置された T 型の値を取得します。 ptr0_get は本質的に安全ではないことに注意してください。 実際に p が型 T の値が配置されたメモリ領域を指し示しているかどうか、なんの保証もないのです。 cast 関数は与えられた値の型を指定した型でキャストします。 この関数もまた本質的に安全ではありません。 pointer.sats で宣言されている ptr0_succ 関数テンプレートは型安全です。 型 T が与えられた時、p よりも型 T のサイズだけ後ろのポインタを返します。

この例の完全なコードは オンライン にあります。

strcmp のような関数を、 人によってはC言語で直接実装するかもしれません。 例えば、上記のATSによる strcmp 実装を翻訳すると、 strcmp のC言語による実装は次のようになるでしょう:

int strcmp (char *p1, char *p2)
{
  int res ;
  unsigned char c1, c2;
  while (1)
  {
    c1 = *p1; c2 = *p2;
    if (c1 > c2) { res =  1; break; } ;
    if (c1 < c2) { res = -1; break; } ;
    if ((int)c1==0) { res = 0 ; break ; } else { p1++; p2++; } ;
  }
  return res ;
}

けれども、C言語スタイルの ATS コードは、直接C言語を書くよりもしばしば利点があります。 例えば、C言語にはサポートがありませんが、ATS は関数テンプレートを直接サポートしています。 C言語では、複雑なマクロを使った関数テンプレート実装を信頼するしかありません。 そのようなコードは理解することが困難であるだけでなく周知の通りエラーを誘発します。 次に ATS における関数テンプレートの実装を見てみましょう。 これは部分的には型安全ではありません。

リストに保存されている要素群を与えられた配列の中にコピーするような関数を作りたいとしましょう。 その関数に array_copy_from_list という名前を付けて、 次のようなインターフェイスを与えます:

fun{a:t@ype} array_copy_from_list (A: array0(a), xs: list0(a)): void

型 T が与えられた時、array0(T) はポインタ p とサイズ n を内包する array0 の値を作り、 その p は型 T の要素を n 個保持するC言語スタイルの配列を指し示します。

差し当り、array_copy_from_list(A, xs) が呼び出されたら、リスト xs の長さに等しい配列Aのサイズを要求しましょう。 ATS における array_copy_from_list の実装を次に示します。 この関数は、unsafe.sats で宣言されている安全でない関数 ptr0_set を使っています:

staload UN = "prelude/SATS/unsafe.sats" (* ****** ****** *) implement {a}(*tmp*) array_copy_from_list (A, xs) = let // fun loop ( p: ptr, xs: list0 (a) ) : void = ( case+ xs of | list0_nil () => () | list0_cons (x, xs) => let val () = $UN.ptr0_set<a> (p, x) in loop (ptr0_succ<a> (p), xs) end // end of [list0_cons] ) (* end of [loop] *) // in loop (array0_get_ref(A), xs) end // end of [array_copy_from_list]

型 T とポインタ p、そして型 T の値 x が与えられた時、 ptr0_set<T> (p, x) は値 x を p によって指し示された位置に保存します。 ptr0_get と同様に ptr0_set は本質的に安全ではありません。 実際に p が型 T の値が配置されたメモリ領域を指し示しているかどうか、なんの保証もありません。 array0.sats で宣言されている array0_get_ref 関数は与えられた array0 値と関連付けられたC言語スタイルの配列のポインタを返します。

この例の完全なコードは オンライン にあります。