次のような2つの抽象型を導入します:
absvtype mynode(l:addr) = ptr(l) absvtype mylist(l:addr, n:int) = ptr(l)NULL ではないアドレス L が与えられた時、型 [mynode(L)] は L に配置されたリストノードを表わします。 NULL ではないアドレス L と整数 N が与えられた時、型 [mylist(L, N)] は最初のノードが L に配置されるような長さ N のリストを表わします。 [mylist] のパラメータの性質は次の2つの証明関数によって捕捉されます:
praxi lemma_mylist_param {l:addr}{n:int} (xs: !mylist(l, n)): [l >= null; n >= 0] void // praxi lemma_mylist_param2 {l:addr}{n:int} ( xs: !mylist(l, n) ) : [(l==null && n==0) || (l > null && n > 0)] void標準的な2つのリストのコンストラクタには次の型が割り当てられます:
// fun mylist_nil () : mylist(null, 0) = "mac#atspre_ptr_null" // fun{} mylist_cons {l1,l2:addr}{n:nat} ( !mynode(l1) >> mylist(l1,n+1), mylist(l2, n) ) :<!wrt> void // end of [mylist_cons] //(1番目の引数である) ノードと (2番目の引数である) リストを繋げるために、[mylist_cons] がなんらかのメモリ割り当てをする必要があるのは明確です。 ときどき、与えられたノードとリストが既に繋がっていて、割り当てが不要なこともあるでしょう。 次の証明関数 [_mylist_cons] はそのような場合を正確に扱うために導入されています:
// prfun _mylist_cons {l1,l2:addr}{n:nat} ( !mynode(l1) >> mylist(l1, n+1), mylist(l2, n) ) :<prf> void // end of [_mylist_cons] //また、[mylist_cons] の変形として [mylist_cons2] も導入されます:
// fun{} mylist_cons2 {l1,l2:addr}{n:nat} (x_hd: mynode(l1), xs_tl: mylist(l2, n)):<!wrt> mylist(l1, n+1) //[mylist] に対する、コンストラクタと正反対の動作をするデコンストラクタ (deconstructor) が次のように宣言されます:
// prfun mylist_unnil {l:addr} (mylist(l,0)): void // fun{} mylist_uncons {l:addr} {n:int | n > 0} ( xs: !mylist(l, n) >> mynode(l) ) : mylist(n-1) // end-of-fun //[mylist_unnil] と [mylist_uncons] の意味は、それらに割り当てられた型からすぐに推察できるでしょう。 [mylist_uncons] の変種として [mylist_uncons2] が次のように与えられます:
// fun{} mylist_uncons2 {l:addr} {n:int | n > 0} (xs: mylist(l, n)): (mynode(l), mylist(n-1)) //[mylist] に対するこのコンストラクタとデコンストラクタの使い方を理解するために、与えられたリストの長さを計算する関数テンプレート [mylist_length] を実装してみましょう:
// fun{} mylist_length{n:int} (xs: !mylist(n)): int(n) //[mylist] の具体的な表現が選択された後でのみ [mylist_cons] と [mylist_uncons] の実装は有効なので、[mylist_cons] もしくは [mylist_uncons] を呼び出す関数も当然コンパイルできません。 従って (そのインスタンスをコンパイルできるように) 関数テンプレートとして [mylist_length] を宣言することが重要になります。 [mylist_length] の実装は次のように与えられます:
implement {}(*tmp*) mylist_length (xs) = let // fun loop {i,j:nat} .<i>. ( xs: !mylist(i), j: int(j) ) : int(i+j) = if isneqz (xs) then let val xs2 = mylist_uncons (xs) val res = loop (xs2, j + 1) prval () = _mylist_cons (xs, xs2) in res end // end of [then] else (j) // end of [else] // prval () = lemma_mylist_param (xs) // in loop (xs, 0) end // end of [mylist_length]記号 [isneqz] は次の関数でオーバーロードされていることに注意してください:
fun
mylist_isnot_nil
{l:addr}{n:int}
(xs: !mylist(l,n)): bool(n > 0) = "mac#atspre_ptr_isnot_null"
ここで紹介した線形リストに対する抽象インターフェイスの全体は mylist.dats から入手できます。
fun{}
mylist_mergesort{n:int} (xs: mylist(n)): mylist(n)
また [mylist_mergesort] の実装は次のようになります:
implement {}(*tmp*) mylist_mergesort (xs) = let // val n = mylist_length (xs) // in mylist_msort (xs, n) end // end of [mylist_mergesort]このとき [mylist_msort] は次の方で宣言されています:
fun{}
mylist_msort{n:int}
(xs: mylist(n), n: int(n)): mylist(n)
[mylist_msort] の実装は次のように与えられます:
implement {}(*tmp*) mylist_msort (xs, n) = let in // if n >= 2 then let // val n1 = half (n) // val (xs1, xs2) = mylist_split (xs, n1) // val xs1 = mylist_msort (xs1, n1) and xs2 = mylist_msort (xs2, n-n1) // in mylist_merge (xs1, xs2) end // end of [then] else xs // end of [else] // end of [if] end // end of [mylist_msort]このとき [mylist_split] はリストを2つに分割し、[mylist_merge] は2つのソート済みリストを1つのリストにマージします。
関数テンプレート [mylist_split] は次のように宣言されます:
// fun{} mylist_split {n:int}{k:nat | k <= n} (xs: mylist(n), k: int(k)): (mylist(k), mylist(n-k)) //また [mylist_split] の実装は次のようになります:
implement {}(*tmp*) mylist_split (xs, k) = let in // if k = 0 then (mylist_nil (), xs) else let val xs_tl = mylist_uncons (xs) val (xs1, xs2) = mylist_split (xs_tl, k-1) in (mylist_cons2 (xs, xs1), xs2) end // end of [else] // end // end of [mylist_split]
関数テンプレート [mylist_merge] は次のように宣言されます:
// fun{} mylist_merge {n1,n2:int} (xs1: mylist(n1), xs2: mylist(n2)): mylist(n1+n2) //また次のコードは [mylist_merge] を実装しています:
implement {}(*tmp*) mylist_merge (xs1, xs2) = let // prval () = lemma_mylist_param (xs1) prval () = lemma_mylist_param (xs2) // in // if isneqz(xs1) then ( if isneqz(xs2) then let val xs1_tl = mylist_uncons (xs1) val xs2_tl = mylist_uncons (xs2) val sgn = compare_mynode_mynode (xs1, xs2) in if sgn <= 0 then let prval () = _mylist_cons (xs2, xs2_tl) in mylist_cons2 (xs1, mylist_merge (xs1_tl, xs2)) end // end of [then] else let prval () = _mylist_cons (xs1, xs1_tl) in mylist_cons2 (xs2, mylist_merge (xs1, xs2_tl)) end // end of [else] end // end of [then] else let prval () = mylist_unnil (xs2) in xs1 end // end of [else] ) (* end of [then] *) else let prval () = mylist_unnil (xs1) in xs2 end // end of [else] // end // end of [mylist_merge][compare_mynode_mynode] は与えられた2つのリストノードに保存されている値を比較する関数テンプレートであることに注意してください。
ここでは [merge_split] と [mylist_merge] のどちらも末尾再帰関数として実装されていないことを指摘したいと思います。 ここでの主な目的はより受け入れやすい表現を得ることでした。 けれども、[merge_split] と [mylist_merge] 双方を末尾再帰関数で再実装することは興味深くやりがいのある課題でしょう。
ここで紹介した抽象線形リストに対するマージソートの実装の全体は mylist_mergesort.dats から入手できます。
[mylist] を [list_vt] として解釈した場合については、mergesort_list_vt.dats を参照してください。 また、[mylist] を [sllist] として解釈した場合については、mergesort_sllist.dats を参照してください。
mylist_quicklist.dats quicksort_list_vt.dats quicksort_sllist.datsこれらはここで紹介したマージソートの実装に沿っています。