Chapter 14. 参照

参照とは本質的にヒープに確保されたサイズ 1 の配列です。 手動で解放できない参照の内容を保管するために確保されたメモリであるという意味で、参照は永続的です。 その代わりに、参照はガベージコレクション (GC) を通じてのみ (安全に) 回収されます。

観型 VT が与えられたとき、観型 VT の値への参照の型は ref(VT) です。 利便性のために、ATS では型コンストラクタ ref は抽象として宣言されます。 けれども、次のような定義は可能です:

typedef ref(a:vt@ype) = [l:addr] (vbox(a @ l) | ptr l)

参照に対する様々な関数インターフェイスを prelude/SATS/reference.sats で見つけることができます。

参照を生成するために、次のインターフェイスを持つ関数テンプレート ref_make_elt を呼び出すことができます:

fun{a:vt@ype} ref_make_elt (x: a):<!wrt> ref a

ref_make_elt に対して略記 ref を使うこともできます。 ref_make_elt が呼び出されるといわゆる wrt-効果 が発生する可能性があることを、記号 !wrt が示すことに注意してください。

参照を通した読み書きのために、 関数テンプレート ref_get_eltref_set_elt を使うことができます。 これらにはそれぞれ次のインターフェイスが割り当てられています:

fun{a:t@ype} ref_get_elt (r: ref a):<!ref> a fun{a:t@ype} ref_set_elt (r: ref a, x: a):<!refwrt> void

ref_get_elt が評価されるといわゆる ref-効果 が発生する可能性があることを、記号 !ref が示すことに注意してください。 同様に、ref_set_elt が評価されると ref-効果 と wrt-効果の両方が起きる可能性があることを、!refwrt は意味しています。 参照 r と値 v が与えられたとき、ref_get_elt(r)ref_set_elt(r, v) はそれぞれ !r!r := v のように書くことができます。

典型的に、参照は永続化状態を記録するのに使われます。 例えば、次のコードはそのような例です:

local // #define BUFSZ 128 // val count = ref<int> (0) // in (* in of [local] *) fun genNewName (prfx: string): string = let val n = !count val () = !count := n + 1 var res = @[byte][BUFSZ]((*void*)) val err = $extfcall ( int, "snprintf", addr@res, BUFSZ, "%s%i", prfx, n ) (* end of [$extfcall] *) in strptr2string(string0_copy($UNSAFE.cast{string}(addr@res))) end // end of [genNewName] end // end of [local]

関数 genNewName は新しい名前を生成するために呼び出されます。 参照 count の内容物である整数は genNewName が呼び出される度に更新されるので、genNewName が返すそれぞれの名前は以前生成されたものとは異なることが保証されます。 C言語の関数 snprintf を直接呼び出すために、$extfcall が使用されていることに注意してください。

参照の誤用 参照は誤用されることが実際にあります。 次のプログラムは、命令型プログラミングを既に学んだ関数型プログラミングの初学者がしばしば書いてしまうものです:

fun fact (n: int): int = let val res = ref<int> (1) fun loop (n: int):<cloref1> void = if n > 0 then (!res := n * !res; loop(n-1)) else () val () = loop (n) in !res end // end of [fact]

関数 fact は次のC言語コードを少々直訳したスタイルで書かれています:

//
int
fact (int n) {
  int res = 1 ;
  while (n > 0) { res = n * res; n = n - 1; } ;
  return (res) ;
}
//

ATS における fact の実装では、res はヒープに確保された参照で、fact 呼び出しが返った後には (GC によって回収されるのを待つ) ゴミになります。 一方、fact のC言語実装における値 res はスタックに確保 (もしくはレジスタにマップされることさえあり得ます) され、fact 呼び出しが返った後にゴミは生成されません。 このC実装を ATS に適切に翻訳すると、次のような参照を使わない実装になるでしょう:

fun fact (n: int): int = let fun loop (n: int, res: int): int = if n > 0 then loop (n, n * res) else res // end of [loop] in loop (n, 1) end // end of [fact]

この実装は参照を全く使いません。

強い正当な理由なしに (動的に生成される) 参照を広範囲にわたる使用することは、しばしば悪いコーディングスタイルの兆しです。

静的に確保された参照 ref_make_elt を呼び出して参照を生成すると、動的なメモリ確保が必要になります。 もしこれが望ましくなかたったり容認できなかったりするなら、 次のように参照の生成に静的なメモリ確保を使うことができます:

var myvar: int = 0 val myref = ref_make_viewptr (view@(myvar) | addr@(myvar))

関数 ref_make_viewptr はポインタとそのポインタに関連した駐観の証明を取り、その証明を消費した後の参照を返します。 ref_make_viewptr はキャスト関数なので、実行時のオーバーヘッドはありません。 上記のコードでは、myvar は静的に確保されていて、その駐観の証明が ref_make_viewptr によって消費された後ではもはや有効ではありません。 C言語では myvarmyref の両方とも単に同じポインタですが、ATS ではそれらは本質的に異なる概念の具象であることは興味深いでしょう: 前者は線形の値であり、後者は非線形の参照なのです。

この章で紹介したコードの全体は オンライン から入手できます。