オズの素晴らしい国、またはsendを使用してデータを受信する方法

かなり前に、並列プログラミングツールに関する情報を収集して、エレガントな(つまり、感覚を表現するのが難しい)言語Oz http://www.mozart-oz.orgに出会いました。 この言語は、Habraコミュニティに導入するに値するように思えました。 そして、それをする時間と理由がありました。



Ozはマルチパラダイムプログラミング言語です。 言語の基本的な抽象化のセットは珍しく、たとえば、情報を送信する送信プロシージャを作成して、その助けを借りてデータを受信することもできます。 そして、キャッチなしで:



send(socket; buffer; flag) = (if (flag == RECV) (recv(socket; buffer)) or (realsend(socket; buffer)))







データの送信と受信は、Oz仮想マシンの同じ操作シーケンスによって実行されるのはまさに事実です。 当然、これは、データおよび並列プロセスを操作するための特別な抽象化によって実現されます。 私の意見では、これらはOzの特徴を非常によく感じることができるので、このテキストはこれらの抽象化の説明に専念しています。 もちろん、オズは以下に述べるもの以上のものですが、私には、toなセンドの秘密はこの言語を初めて知り、それを楽しむのに適した素材であるようです。







0.はじめに




Port



コンストラクトを実装するプログラムのテキストから始めることができます。これは、sendプロシージャを使用してデータが送信されるときに送信されます。 Port



の使用例は、同じプログラムに含まれています。 ソースコード(このテキストの固有名):



 
	ポートを宣言する 
	 
	ローカルPortTag NewPort IsPort 
		 PortTag = {NewName} 
	 
		楽しい{NewPort FS} 
			ローカルSC 
				 C = {NewCell S} 
				 FS = !! S 
				 {NewChunkポート(PortTag:C)} 
			終わり 
		終わり 
	 
		楽しい{IsPort?P} 
			 {Chunk.hasFeature P PortTag} 
		終わり 
	 
		 proc {PMを送信} 
			地元のMsさん 
				 {Exchange P.PortTag Ms Mr} 
				 Ms = M |  !! Mr 
			終わり 
		終わり 
	 
		ポート=ポート 
		 ( 
			新規:NewPort 
			 is:IsPort 
			送信:送信 
		 ) 
	終わり 
	 
	 NewQueueServerを宣言する 
	 
	 fun {NewQueueServer} 
		与えられたギブポートのローカル 
			 GivePort = {Port.new Given} 
			 TakePort = {Port.new Taken} 
	 
			 proc {Jos Xs Ys} 
				ローカルXr Yr XY in 
					 Xs = X |  Xr 
					 Ys = Y | 年 
					 X = Y 
					 {Xr Yrに参加} 
				終わり 
			終わり 
	 
			スレッド{Join Given Taken}終了 
		 
			待ち行列 
			 ( 
				 put:proc {$ X} {Port.send GivePort X} end 
				 get:proc {$ X} {Port.send TakePort X} end 
			 ) 
		終わり 
	終わり 
	 
	 Qを宣言する 
	 
	スレッドQ = {NewQueueServer}終了 
	 
	 {Q.put 1} 
	 {{Q.get $}を参照} 
	 {{Q.get $}を参照} 
	 {Q.put 2} 




ご覧のとおり、キャッチはありません。 両方のqueueserver関数-putおよびget-は、名前に対応するセマンティクスを持つSend



プロシージャを使用して実装されます。 このコードは短くなる可能性があります-Ozでは構文の砂糖で十分であり、必要な説明の量を減らすために上記では使用されていません。



このコードがどのように機能するかを理解するには、(1)Ozプログラム実行のモデルを扱う必要があります。変数、スレッド、アライメント演算子( =



); (2)手順。 (3)セル(セル)、メモリの領域(チャンク)および将来(未来)。



1.変数、スレッド、演算子=







前述のとおり、Ozはマルチパラダイム言語です。 ウィキペディアでは、機能的、論理的、命令的、オブジェクト指向、分散、並列(並行という意味で)、および制限付きプログラミング用の言語として記述されています。 しかし、それは別のパラダイム-データフローを使用したプログラミング(データフロープログラミング)に基づいています。 Ozは、データストリームを使用したコンピューティングの元の(CPUアーキテクチャで使用される)概念に非常に近いこのパラダイムを実装し、次のように実行します。



1.1。 Ozでのすべての計算は、ストアに含まれるデータを使用して行われます。 同時に、ストレージは非常に抽象的であり(内部の仕組みはプログラマーから隠されており、ストア内の情報は特定のインターフェイスを介してのみ制御できます)、分散されています(Ozコンピューティングのすべての参加者は、ネットワーク上に散在するものも含めてストアで作業できます) ストアは、3つの異なる方法で情報を保存できます。 (1)自由または関連する論理変数の形式。 (2)メモリのセルではなく、変数への名前付き可変ポインタである可能性が高いセルの形式。 (3)Ozでクロージャと呼ばれるプロシージャの形式(当然、これらは1次オブジェクトです。つまり、プログラムで明示的に操作できます)。



1.2。 計算は、スレッドが実行されるときに行われます。 同時に、Ozのスレッドはデータフローです。 つまり、特定の一連のステートメント(ステートメント)が次々に実行されます(すべて命令型言語のように)が、1つの警告があります。演算子は、使用されるすべての変数が接続されている場合にのみ実行されます。 それ以外の場合、スレッドは一時停止し、必要な変数をバインドする瞬間を待ちます。 実際、使用されるデータの準備が整ったときに特定の演算子を実行することが、日付フロー計算の主な方法です。



Ozでのスレッドの作成は、 thread ... end



構文を使用して非常に簡単です。 これはfork()



スタイルで作成されます。 つまり、生成されたスレッドは、 thread ... end



操作の時点で親スレッドが使用できたすべての変数(つまり、変数)にアクセスできます。



1.3。 Ozの変数は論理的です。



Ozスタイルのブール値は、別の変数と同一視できる1回バインドされた変数です。



一般に、この定義(マニュアルからの翻訳)はOzの変数についてほとんど述べていません。 彼らはあなたがあなた自身でもう少しできるようにします。 また、通常のc / javascript / haskell変数とは大きく異なります。変数は、名前付きメモリセル(c / js)または名前付き値(haskell)です。 おそらくOzの変数の最良のアイデアには、いくつかの技術的な詳細の説明が与えられます。



1.3.1。 Ozの変数のライフパスは、 local ... in ... end



またはdeclare ... in ...



構文を使用して宣言されているという事実から始まります。 これらの構造の違いは、 local ...



ではin ... end



内の操作でのみ使用する変数を宣言でき、declareで宣言された変数は、対応するin



後のすべての場所で使用できることです。 当然、 declare



は、モジュールのグローバルスコープでのみ変数を定義するために使用できます。



変数の宣言は、ストア内に新しいノードが作成され、宣言された変数が参照することです。 また、 unknown



値がノードに書き込まれ、変数がデータに関連付けられていないことが示されます。 スレッドがデータに対してそのような変数を呼び出すと、対応するノードがデータに関連付けられるまで、スレッドは中断されます。 例(コード1):



 
	ローカルX 
		スレッド{Browse 2 * X} end 
		 X = 5 
	終わり 




Browse



は、引数を出力する標準のOzルーチンの1つです。 この例でX



宣言されると、変数が作成され、ストア内の対応する新しいノードが作成されます。 Browse



X



を介してこのノードにアクセスすると、対応するX



ノードに値が表示されるまで、対応するスレッドの実行が中断されます(ノードの値とノードへの変数のバインドの両方がここで変更できます)。



1.3.2。 ノードの値は、統合(統合)または(別の名前)増分再カウント(増分テル)のプロセスに配置されます。 式のペアの統一は、 =



(等号演算子)演算子によって行われます。 同じプロセスで、ノードへの変数のバインドも変更できます。 コードE1 = E2



の式のペア(式の場合)の統合アルゴリズムは、このように機能します(E1およびE2の可能性のあるオプションの分析を伴う再帰的手順)。



U.1。 計算E1およびE2の結果は、原子型Ozの値です(原子は不可分な型です:整数、浮動小数点数、リテラル-文字列など)。 この場合、これらの値が等しい場合、演算子が実行され、不等式が満たされると、例外がスローされます(Ozの例外はかなり標準的であり、 try ... catch ... finally ... end



; finally



-optional)を使用して処理されます。



U.2。 E1



の計算結果は変数であり、 E2



の計算結果は何らかのタイプの値です(この状況と対称的なのは、 E1



計算が何らかのタイプの値になり、 E2



が変数になることです)。 次のようになります。



 
	 X =(Y + Z)* 5 




この場合、すべてが次のように機能します。



U.2.1。 E1



によって設定された変数は、不明なノードを参照しています。 その場合、 E2



計算によって得られた値がこのノードに書き込まれ、その結果、 E2



変数が接続されます。



U.2.2。 E1



変数がすでにアトミック値に関連付けられている場合、対応するノード内の値がE2



値と比較され、タイプまたは値自体が一致しない場合、例外がスローされます。 それ以外の場合、ステートメントは終了します。



code-1から変数X



をバインドするのはこのルールです。 値(5)に関連付けられた後、呼び出されたプロシージャBrowse



内でこのバインディングを待機する演算子が実行されます。 そして、コード-1が次のようになった場合、結果はまったく同じになることは明らかです。



 
	ローカルX 
		スレッド{Browse 2 * X} end 
		 5 = X 
	終わり 




U.2で。 E1-変数が複合型の値に関連付けられている場合、別のオプションが可能ですが、後でU.4段落で検討されます...



U.3。 E1



E2



両方が何らかの変数を定義します。 可能なオプションもあります。



U.3.1。 変数の1つがバインドされていないか、両方の変数がバインドされていないことがわかります(つまり、値が不明なノードを参照しています)。 この場合、関係のない変数の1つのノード、または接続されていない変数の不明なノードは破棄されます。 次に、このノードを以前に参照した変数が変更され、他の変数と同じノードを参照し始めます。



code-2を実行することにより、なぜここで既に明確になっているはずです。



 
	ローカルXYZ 
		スレッド{Browse X + Y + Z} end 
		 X = Y 
	 %Z = X + YX = 10 
		 X + Y = Z 
	終わり	 




Ozの値は40になります。また、コメントを削除すると( %



始まる)Ozが「フリーズ」します。



U.3.2。 両方の変数は、値が書き込まれるノードを参照します。 この場合、動作は次のとおりです。 これらが異なるタイプの値である場合、または同じタイプの異なるアトミック値である場合、例外がスローされます。 これらがアトミックタイプの等しい値である場合、オペレーターは実行を完了します。 ただし、ノードは複合型の値を格納することもできます。



Ozの主要な複合型はレコードです。 構文的には、エントリは次のようになります。



 
	ラベル(feature0:field0 feature2:field2 ... featureN:fieldN) 




おおよそ、上記は閉じられたレコードであるため、フィールド(フィールド)の数は固定されており、レコードも開いています。 レコードフィールドには、対応するプロパティ(機能)の名前で、ドットを介してアクセスできます。 構造内のフィールドの数は、アリティと呼ばれます。 より具体的な例:



 
	 U = habrauser(ニックネーム: 'ミカノイド'カルマ:10強度:10) 
	 K = U.karma 




微妙:レコードタイプの値自体のフィールドはOz変数です。 上記の例では、レコードタイプとhabrauserラベルを使用して値を作成すると、記述されたアルゴリズムに従ってアトミックタイプ値で統合された3つの変数が作成されます: 'mikhanoid'、10および10。ただし、そのような値の代わりに、変数フィールドを任意の式で統合できます。 この時点で、このコードがどのように機能するかが明確になるはずです。



 
	地元のUKS 
 (*)U = habrauser(ニックネーム: 'ミカノイド'カルマ:K強度:S) 
		 K = s 
		 10 = K 
	終わり 




行(*)では、非バインド変数U



U.2.1ルールに従ってレコードタイプ値U



統合されています。



Ozレコードには重要な役割があります-それらはストアを有向グラフ構造に変換します:ノードがタイプレコードの値を格納する場合、その変数フィールドは他のノードを指します。 また、記載されているアルゴリズムは、U.4。のおかげで、グラフマージアルゴリズムと見なすことができます。



U.4。 ここでは、(1)式E1



E2



両方がレコードタイプの値である、(2)レコードが格納されるノードを参照する変数を定義する、(3) E1



E2



いずれかがレコードタイプの値を設定する、 another-レコードが保存されているノードに関連付けられた変数を設定します。 これらのすべての場合、エントリに(a)異なるラベル、または(b)異なるアリティ、または©異なるプロパティセットがある場合、例外がスローされます。 レコードがポイント(a)、(b)、および©で一致する場合、異なるレコードの変数のペアは同じプロパティ名で統合されます。



この時点で、そのようなコードの作業の結果として、理由がすでに明確になっているはずです。



 
	ローカルWXYZ 
		 W = XZ = Y 
		 f(a:10 b:X)= f(a:Y b:20) 
		 {W + Zを参照} 
	終わり 




値30が表示されます。



別の興味深い例はこのコードです:



 
	ローカルZ 
		 Z = f(Z 20) 
		 {Zを参照} 
	終わり 




ここで、 f



は、フィールドのプロパティが1からそのアリティまでの整数名を自動的に取得するレコードの変形です。 Ozでは、そのようなレコードはタプルと呼ばれ、論理プログラミングの複合語の類似物です。 上記のコードでは、次のことが起こります。 (1)タイプrecordの値は2つのフィールドで作成されます。つまり、2つの変数が作成されます。 (2)プロパティ1



フィールド1



、無関係な変数Z、20の原子値を持つ2番目のフィールドと統合されます。その結果、 f



の値は、変数Z



および値20を格納するノードを指す未知のノードを指します。 Z



指すノードにはレコードタイプの値が書き込まれ、その2番目の要素(変数Zなど)はこのノードを指します。 特別なことは何もありません。ストアグラフにループがあります。 Browse



は、 R10 = f(R10 20)



ます。



Ozのリストは一種のタプルです。 それらは機能的および論理的プログラミングのために標準的に編成されています。リストはタプルであり、最初の要素は変数の先頭で、2番目は末尾を構成するリストです。 リストの末尾の頭を指定するには、記号|



使用します|



Head | Tail



Head | Tail







2.手順




Ozでは、プロシージャを使用してステートメントのシーケンスを抽象化できます。 Ozのプロシージャは値(ファーストクラスオブジェクト)であるため、変数に自由に「割り当てる」ことができます。 より詳細に。 Ozは、何らかのコードとしてストアにプロシージャを実装するクロージャーを格納します。 このような各閉鎖手順は、ストア全体の一意の名前を取得します。 Ozの名前はリテラル-原子型の値です。 プロシージャコードを作成し、一意の名前を割り当てた後、名前はいくつかの変数が参照するノードに書き込まれます。 その後、この変数を介してプロシージャを呼び出すことができます。



そのため、ソースコードでは、 NewPort



IsPort



Send



、...がlocal



またはdeclare



を使用して変数として宣言されています。



プロシージャを定義する基本的な構成体はproc



です。



 
	ローカル... P ... in 
		 ... 
		 proc {P X1 ... XN} S1 ... SM終了 
		 ... 
	終わり 




X1



、...、 XN



は正式な引数です。 S1



、...、 SM



は、プロシージャを実装する一連の演算子です。 プロシージャは、中括弧を使用して呼び出されます。



 
	 {P A1 ... AN} 




A1



、...、 AN



は実パラメータです:式、変数など。 呼び出しは、仮パラメータに対応する新しい変数の宣言で発生します。仮パラメータは引数と統合されており、その中には無関係の変数が存在する場合があります。 したがって、プロシージャは、任意のパラメータを介してデータを受信および返すことができます。 したがって、コードの可読性を高めるために、プログラマーが値を返すためだけに使用することを期待する変数は、接頭辞でマークできます?



これは単なるコメントです。



プロシージャの名前を格納するノードに関連付けられた変数は、次の例のように、式を評価した結果になる場合もあります。



 
	 {Q.put 1} 




Ozには匿名プロシージャを使用する機能がありますが、これは通常とは異なる方法(ネストメカニズムを使用)で実装されています。 {...}



囲まれた一連の演算子では、位置の1つがシンボル$



-添付ファイルのラベルで占められている場合があります。 Ozが中括弧内でこのようなマークに遭遇し、何らかの演算子を実行すると、(1) {... $ ...}



数だけ新しいローカルスコープに新しい変数を自動的に作成し、このスコープに(2) {... $ ...}



呼び出し、マーカーの代わりにこの呼び出しを新しい変数に置き換え、(3)これらの変数を実行可能ステートメントの{... $ ...}



場所に挿入します。 例えば



 
	ローカルP in 
		 proc {PX?Y} Y = X + 10 end 
		 {{P 20 $} + {P 30 $} + 40を参照} 
	終わり 




として実行されます



 
	ローカルP in 
		 proc {PXY} Y = X + 10終了 
		ローカルX1 X2 
			 {P 20 X1} 
			 {P 30 X2} 
			 {X1 + X2 + 40を参照} 
		終わり 
	終わり 




ネストマークは{...}



内の最初の場所でも使用できますが、プロシージャの定義中のみです。 ただし、接続メカニズムはまったく同じように機能します。 コード:



 
	ローカルP in 
		ローカルP1 
			 proc {P1 X1 ... XN} S1 ... SM終了 
			 P = P1 
		終わり 
	終わり 




そして



 
	ローカルP in 
		 P = proc {$ X1 ... XN} S1 ... SMエンド 
	終わり	 




同等です。



Oz関数もプロシージャであり、プロシージャが確実に値を返さなければならないことがわかっている場合の構文の簡略化です。 次に、1つの仮パラメーターの説明を保存できます。つまり、 fun {F X1 ... XN} S1 ... SM end



proc {F X1 ... XN Y} S1 ... Y = SM end



と同じproc {F X1 ... XN Y} S1 ... Y = SM end



ます。 呼び出し{F A1 ... AN}



、関数として、Ozは自動的にプロシージャ{F A1 ... AN $}



呼び出しに変わります



たとえば。



 
	楽しい{FX} 
		ローカルK 
			 K = X / 20 
			 {Kを参照} 
		終わり 
		ローカルL 
			 L = 30 
			 X == L 
		終わり 
	終わり 




定義を満たしている



 
	 proc {FXY} 
		ローカルK 
			 K = X / 20 
			 {Kを参照} 
		終わり 
		 
		 Y =ローカルL 
			 L = 30 
			 X == L 
		終わり 
	終わり 




この場合、 local ... in ... end



ブロックの値は、その中の最後のステートメントの値です。 つまり、この例では、 F



は、質問に対する答えを得るための呼び出しの結果になります。関数30の最初で唯一の引数は等しいです。



3.セル、メモリ領域(チャンク)、および将来(将来)




ストア内のデータを処理するために、Ozはさらにいくつかの抽象化をサポートしています。



3.1。 ストレージ領域は、ストア内のエントリの一部です。 ただし、通常のレコードとは異なり、それらは一意の名前を使用して識別され(手順と同様)、アリティを判別することはできません。 つまり、それらのコンポーネントには、プロパティ名(機能)によってのみアクセスできます。 これらの名前がコードの一部から隠されている場合、これらの領域では、対応するメモリ領域の要素へのアクセスは不可能になります。 エリアは、 {NewChunk Record}



関数を呼び出すことで作成されます。 呼び出しは、一意の名前でメモリ領域を作成し、その名前を返します。 名前は、何らかの変数によって参照されるノードに書き込むことができます。 次に、この変数と演算子を使用し.



メモリ領域のフィールドにアクセスできます:



 
	ローカルXR 
		 R = f(a:1 b:2 c:3) 
		 X = {NewChunk R} 
		 {Xcを参照} 
	終わり 




3.2。 Ozのセルは、状態で動作するように設計されています。 ストア内のプロシージャおよびメモリ領域としてのセルは、一意の名前で定義されます。 変数と同様に、それはあるノードへのポインタですが、変数とは異なり、セルが指すノードは明示的に繰り返し設定できます。 セルは次のインターフェースを定義します。



C = {NewCell E}



-式E



と変数の値の統一から生じるノードを指すセルを作成します-手続きの仮引数NewCell



。新しく作成されたセルの新しい一意の名前が返され、変数に割り当てられますC



。セルからノードへのリンクは、もちろん、ガベージを収集するときに考慮されます。{IsCell C}



質問に答えます:はC



セル名に関連付けられた変数です



@C



-結果としてのこの式には、変数に格納された名前のセルが参照するノードを指す変数がありますC



C := E



名前が格納されているセルのポインタを変更しますC



その結果、関係のない名前のない変数と式の結果の統合の結果として得られたノードを指しますE







{Exchange C E1 E2}



-不可解な1つのアトミック操作は@C



E1



統合さ、実行されC := E2



ます。



たとえば、セルはカウンターとして使用できます。



 
	ローカルC 
		C = {NewCell 0} 
		{Exchange CXスレッドX + 1終了} 
	終わり 




ここで排除できませんthread ... end



。そして、これは、実行中の各スレッドに対して、Ozが最後のスレッド本体演算子と統合される非バインド変数を自動的に作成するという理由で機能します。そして、この例のこの変数は最後の引数Exchange



です。



3.3。将来のものは最初にSmalltalk-72で提案され、その後、それらのアプリケーションのアイデアはMultiLISP(1985)で開発されました。 Futureは、かなり一般的なツールです。Javaにあり、java.util.cuncurrent.Future



; からアクセスできます。それらのサポートはC ++ 0xで計画されています。それらはOzでサポートされていますが、珍しい形式です。



何らかの式の未来E



は、非同期コンピューティングに関連するオブジェクトE



です。将来的には、未確定の結果を伴ういくつかの操作が可能になります。E



-たとえば、関数呼び出しで使用したり、リストに書き込んだり、他の非同期計算に渡したりします。作業を継続するために必要なのが式の値である場合、E



その未来へのアピールE



は、計算されるまで実行を一時停止します。



Ozでは、通常の変数には同様のプロパティがあります。変数のあるアクションは、参照するノードに値が現れるまで待たずに実行できます。すでに示した例の多くはこれを示しています。ただし、Ozの変数は情報交換の双方向チャネル=



です。式の左右に立つことは、統合アルゴリズムに対して対称的です。したがって、コード内では:



 
	ローカルX 
		スレッドX = 5終了 
		スレッドX = 7終了 
		X = 3 
		{Xを参照} 
	終わり 




データを生成するスレッドとして目立つものはありません。統合プロセスの例外により、スレッドが発生する可能性があります。将来は、この状況で何らかの秩序を確立することができます。Ozでは、将来はノードへの読み取り専用リンク、つまり変数の多少制限されたバージョンです。未来が統合プロセスに参加する場合、このプロセスは未来に対応する変数が接続されるまで中断されます。X



Ozの変数の将来は、演算子によって形作られ!!X



ます。上記の例の順序は、たとえば次のように入力できます。



 
	ローカルXY 
		Y = !! X 
		スレッドY = 5終了 
		スレッドX = 7終了 
		Y = 3 
		{閲覧Y} 
	終わり 




ここでは、値は常に2番目のスレッドでのみ形成されます。



4.すべて一緒に




このテキストを最初にスクロールする必要がないように、導入部からコードを再び持ってくると便利です。



 
00 declare Port in 
01	 
02 local PortTag NewPort IsPort Send in 
03 PortTag = {NewName} 
04	 
05 fun {NewPort FS} 
06 local SC in 
07 C = {NewCell S} 
08 FS = !!S 
09 {NewChunk port(PortTag: C)} 
10 end 
11 end 
 12	 
13 fun {IsPort ?P} 
14 {Chunk.hasFeature P PortTag} 
15 end 
 16	 
17 proc {Send PM} 
18 local Ms Mr in 
19 {Exchange P.PortTag Ms Mr} 
20 Ms = M | !!Mr 
21 end 
22 end 
 23	 
24 Port = port 
25 ( 
26 new: NewPort 
27 is: IsPort 
28 send: Send 
29 ) 
30 end 
 31	 
32 declare NewQueueServer in 
 33	 
34 fun {NewQueueServer} 
35 local Given GivePort Taken TakePort Join in 
36 GivePort = {Port.new Given} 
37 TakePort = {Port.new Taken} 
 38	 
39 proc {Join Xs Ys} 
40 local Xr Yr XY in 
41 Xs = X | Xr 
42 Ys = Y | Yr 
43 X = Y 
44 {Join Xr Yr} 
45 end 
46 end 
47	 
48 thread {Join Given Taken} end 
 49		 
50 queue 
51 ( 
52 put: proc {$ X} {Port.send GivePort X} end 
53 get:proc {$ X} {Port.send TakePort X} end 
54) 
55終わり 
56終わり 
 57	 
58でQを宣言する 
 59	 
60スレッドQ = {NewQueueServer}終了 
 61	 
62 {Q.put 1} 
63 {{Q.get $}を参照} 
64 {{Q.get $}を参照} 
65 {Q.put 2} 




プログラム全体を行ごとに記述することは意味がありませんが(非並列プログラムでさえもどのように機能するかについてはほとんどわかりません)、いくつかの点を明確にする必要があります。



4.1。作成Port







Port



レコードを表し、フィールドは一連のプロシージャに関連付けられます。これは、いくつかの手順を便利にグループ化したものに過ぎません。ポート自体は、関数によって一意性が保証されている名前の下のセルを含むメモリ領域NewName



です。変数はPortName



、ローカルユニット(02から30)に表示されているので、あなたは、メモリ領域のコンポーネントにアクセスできる唯一の手順IsPort



NewPort



Send



。このブロックの外でPortName



、変数について何も知られていないため、ポートの状態を保存するメモリ領域のフィールドへのアクセスはほとんどありません(もちろん、名前は推測できますが、このメカニズムはカプセル化には十分です)。



機能に注意を払う必要がありますNewPort



(05-11)。ポートメモリ領域自体を返すことに加えて、引数は、その最初の引数を通じて、セルを初期化するfuture変数も返します。これは、初期化Given



Taken



(36-37)に使用されます



4.2。手順Send



(17-22)。



ポートメモリ領域のセルは、常に不明なノードを指していることに注意してください。そしてSend



、次の呼び出しで保証される最初のことは、前のノードへのリンクが変数Ms



入力され、その代わりに、非バインド変数のノードへのリンクを配置することですMr



。次に、Send



変数の値が形成されますMs



。これは、リストタプルとの統合中に取得され、送信されたメッセージ(関連していない可能性があります)が最初の場所にあり、未来が2番目にありますMr



(再び:リストは2番目のタプルです)何でも我慢できます)。後続の呼び出しSend



は、変数で同じことを行いますMr



、およびこのプロセスは、セルを初期化するために使用された変数を徐々にに変え、NewPort



その将来が外部に返されて、リストに変えます。言い換えれば、Send



実際にキューでメッセージを送信します。



4.3。手順Join



(39-46)。



ここで最も重要なことは、この手順が適用される対象であり、別のスレッドで動作します(48)。これはとに適用されGiven



Taken



呼び出しの結果としてSend



徐々にリストに変わります。実際のパラメーターJoin



は常に未来であるため、結合演算子(41-42)はバインド後にのみ実行されXs



Ys



で発生しSend



、これらの変数をタプルを表示するリンクに変換しますMessage | SomeFuture



。その後Message



、着信(Given



)および発信(Taken



)フローコンポーネントが統合され、Join



将来のリストの末尾に呼ばれます(量子力学に少し似ていますよね?アインシュタインは、隠された変数で宇宙のモデルを構築するための並列プログラミングの知識を欠いていたのでしょう)。



4.4。 最後に。



バインドされた変数をGiven



使用してストリームに設定されたSend



変数は、スレッド(48)でストリームに設定された対応するバインドされていない変数と統合されますTaken



もちろん、状況は完全に対称的であり、そして名前put



とはget



便宜上使用されています。ただし、キューサーバーはまだ非同期であり、便利です。



5. Ozのその他の興味深い機能




Ozは他の機能(およびそれらの多くは広大です)の中でも特に興味深い並列プログラミングモデルを提供します。Prologで採用されている古典的なものとは異なります。たとえば、プログラマーがオプションを検索および列挙するための独自の手順を定義できるという点です。そして、もちろん、Ozが採用している論理プログラミングモデルは並行しています。



All Articles