send(socket; buffer; flag) = (if (flag == RECV) (recv(socket; buffer)) or (realsend(socket; buffer)))
.Port
, through which data is transmitted using the send procedure. This program also includes an example of using Port
. Source code (proper name in this text):declare Port in local PortTag NewPort IsPort Send in PortTag = {NewName} fun {NewPort FS} local SC in C = {NewCell S} FS = !! S {NewChunk port (PortTag: C)} end end fun {IsPort? P} {Chunk.hasFeature P PortTag} end proc {Send PM} local Ms Mr in {Exchange P.PortTag Ms Mr} Ms = M | !! Mr end end Port = port ( new: NewPort is: IsPort send: send ) end declare NewQueueServer in fun {NewQueueServer} local Given GivePort Taken TakePort Join in GivePort = {Port.new Given} TakePort = {Port.new Taken} proc {Join Xs Ys} local Xr Yr XY in Xs = X | Xr Ys = Y | Yr X = y {Join Xr Yr} end end thread {Join Given Taken} end queue ( put: proc {$ X} {Port.send GivePort X} end get: proc {$ X} {Port.send TakePort X} end ) end end declare Q in thread Q = {NewQueueServer} end {Q.put 1} {Browse {Q.get $}} {Browse {Q.get $}} {Q.put 2}
put
and get
— are implemented using the Send
procedure, the semantics of which corresponds to its name. This code could be shorter - Oz has enough syntactic sugar, which is not used above in order to reduce the amount of necessary explanations.=
); (2) procedures; (3) cells (cells), memory areas (chunks) and future (futures).=
thread ... end
construct. Creation of this occurs in the fork()
style. That is, the generated thread has access to all the variables (namely variables) that were available to the parent thread at the time of the thread ... end
operation.local ... in ... end
or declare ... in ...
The differences between these constructions are that local ...
allows declaring variables only for use in operations that are inside in ... end
, and variables declared using declare
will be available everywhere after the corresponding in
. Naturally, declare
can be used to define variables only in the global scope of a module.unknown
written to the node, indicating that the variable is in no way associated with the data. If a thread accesses such a variable, it will be suspended until the corresponding node is associated with the data. For example (code-1):local X in thread {Browse 2 * X} end X = 5 end
Browse
is one of the standard Oz procedures that prints its argument. When X
declared in this example, a variable will be created and a new node in the store corresponding to it. When Browse
accesses this node through X
, the execution of the corresponding thread will be suspended until the value appears in the corresponding X
node (the value in the node itself and the variable binding to the node may change here).=
(equality operator). In the same process, the binding of variables to nodes may change. The unification algorithm for a pair of expressions (specifically for expressions) in the code E1 = E2
works like this (a recursive procedure with an analysis of the variants of what E1 and E2 can be).try ... catch ... finally ... end
; finally
; optionally).E1
is some variable, and the result of the calculation of E2
is a value of some type (symmetric to this situation is the one in which the calculation of E1
leads to a value of a certain type, and E2
to a variable). It can disappear like this:X = (Y + Z) * 5
E1
refers to unknown node. If so, then the value obtained by the calculation of E2
written to this node, as a result of which the E1
variable is bound.E1
variable is already associated with an atomic value, then the value that is in the corresponding node is compared with the E2
value, and if the types do not match or the values themselves, an exception is generated. Otherwise, the statement ends.X
linked from code -1. After it is associated with the value (5), the statement inside the called procedure Browse
is executed, waiting for this binding. And here it should be clear that the result would be exactly the same if code-1 looked like this:local X in thread {Browse 2 * X} end 5 = X end
E1
variable is associated with the value of a composite type, but it will be discussed later in paragraph U.4 ...E1
and E2
define some variable. Here, too, options are possible.local XYZ in thread {Browse X + Y + Z} end X = y % Z = X + YX = 10 X + Y = Z end
%
).label (feature0: field0 feature2: field2 ... featureN: fieldN)
U = habrauser (nick: 'mikhanoid' karma: 10 strength: 10) K = U.karma
local UKS in (*) U = habrauser (nick: 'mikhanoid' karma: K strength: S) K = S 10 = K end
U
unified with the value of the record type according to rule U.2.1.E1
and E2
are record type values, (2) both expressions define variables that refer to nodes in which records are stored, (3) one of E1
, E2
sets a record type value, and another - sets the variable associated with the node in which the record is stored. In all these cases, if the records have (a) different labels, or (b) different arity, or different sets of properties, an exception is generated. If the records coincide in points (a), (b) and ©, then unification of pairs of variables from different records with the same property names occurs.local WXYZ in W = XZ = Y f (a: 10 b: X) = f (a: Y b: 20) {Browse W + Z} end
local Z in Z = f (Z 20) {Browse Z} end
f
is a variant of the record in which the field properties automatically get integer names from 1 to its arity. In Oz, such records are called tuples and are analogs of compound expressions (compound term) in logic programming. In the code above, the following happens. (1) creates a value of type record with two fields, that is, two variables are created. (2) the field with property 1
unified with the unbound variable Z, the second field with an atomic value of 20. In the end, the value of f
points to the unknown-node pointed to by both the variable Z
and the node storing the value 20. (3) to unknown -node to which Z
points a value of type entry, the second element of which (like the variable Z) points to this node. Nothing special, just get a cycle in the column store. Browse
prints it like this: R10 = f(R10 20)
.|
: Head | Tail
Head | Tail
NewPort
, IsPort
, Send
, ... are declared as variables using local
or declare
.proc
:local ... P ... in ... proc {P X1 ... XN} S1 ... SM end ... end
X1
, ..., XN
are formal arguments. S1
, ..., SM
is a sequence of statements implementing the procedure. Procedures are invoked using braces:{P A1 ... AN}
A1
, ..., AN
are valid parameters: expressions, variables, and so on. The call occurs with the declaration of new variables corresponding to the formal parameters, which are unified with the arguments, among which may be unrelated variables. Therefore, the procedure can both receive and return data through any parameter. Therefore, to increase the readability of the code, variables that the programmer intends to use only for returning values can be marked with a prefix ?
which is just a comment.{Q.put 1}
{...}
one of the positions can be occupied by the $
symbol - an attachment label. When Oz encounters such labels inside curly brackets, executing some operator, it (1) automatically creates new variables, according to the number {... $ ...}
, in the new local scope, then, in this scope (2) makes calls {... $ ...}
, replacing the marker by substituting a new variable created for this call, and (3) inserts these variables into the {... $ ...}
places in the executable statement. for examplelocal P in proc {PX? Y} Y = X + 10 end {Browse {P 20 $} + {P 30 $} + 40} end
local P in proc {PXY} Y = X + 10 end local X1 X2 in {P 20 X1} {P 30 X2} {Browse X1 + X2 + 40} end end
{...}
, but only during the procedure definition. But the investment mechanism will work the same way. Codes:local P in local P1 in proc {P1 X1 ... XN} S1 ... SM end P = P1 end end
local P in P = proc {$ X1 ... XN} S1 ... SM end end
fun {F X1 ... XN} S1 ... SM end
the same as proc {F X1 ... XN Y} S1 ... Y = SM end
. Calling {F A1 ... AN}
, as a function, Oz automatically turns into a procedure call {F A1 ... AN $}
fun {FX} local K in K = X / 20 {Browse K} end local L in L = 30 X == L end end
proc {FXY} local K in K = X / 20 {Browse K} end Y = local L in L = 30 X == L end end
local ... in ... end
block is the value of the last operator in it. That is, in this example, F
will be the result of its call to have the answer to the question: is the first and only argument of function 30 equal?{NewChunk Record}
. The call creates a memory region with a unique name, and returns this name. The name can be written to the node referenced by some variable. Then through this variable and the operator .
You can refer to the fields of the memory area:local XR in R = f (a: 1 b: 2 c: 3) X = {NewChunk R} {Browse Xc} end
C = {NewCell E}
- creates a cell pointing to the node that will result from the unification of the value of the expression E
and the variable - the formal argument of the procedure NewCell
. The new unique name of the newly created cell is returned and assigned to the variable C
. The link to the node from the cell, of course, is taken into account when garbage collection. {IsCell C}
answers the question: is the variable associated C
with the cell name?@C
- this expression has the result of its own variable, pointing to the node on which the cell with the name stored in the variable refers C
. C := E
changes the pointer in the cell whose name is stored in C
, so that it points to the node obtained as a result of the unifications of some unrelated nameless variable and the result of the expression E
.{Exchange C E1 E2}
- for one inseparable, atomic operation unifies @C
with E1
and, then, performs C := E2
.local C in C = {NewCell 0} {Exchange CX thread X + 1 end} end
thread ... end
. And it works for the reason that for each running thread, Oz automatically creates an unbound variable that is unified with the last of the thread body operators. And this variable in this example is the last argument Exchange
.java.util.cuncurrent.Future
; their support is scheduled in C ++ 0x. They are supported in Oz, but in an unusual form.E
is an object associated with asynchronous computing E
. The future allows you to perform some operations with a result that E
is not yet computed — for example, use it in function calls, write to lists, transfer to other asynchronous calculations, and so on. In the case when the continuation of work requires exactly the value of the expression E
, the reference to its future will suspend execution until the moment when it E
is calculated.=
expression are symmetrical for the unification algorithm. Therefore, in the code:local X in thread X = 5 end thread X = 7 end X = 3 {Browse X} end
X
in Oz is formed by the operator !!X
. The order in the example above can be made, for example, as follows:local XY in Y = !! X thread Y = 5 end thread X = 7 end Y = 3 {Browse Y} end
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 sixteen 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 end 56 end 57 58 declare Q in 59 60 thread Q = {NewQueueServer} end 61 62 {Q.put 1} 63 {Browse {Q.get $}} 64 {Browse {Q.get $}} 65 {Q.put 2}
Port
.Port
is a record, the fields are associated with a set of procedures. This is nothing more than a convenient grouping of several procedures. The port itself is a memory area containing a cell under the name, whose uniqueness is guaranteed by the function NewName
. The variable PortName
is visible only in the local unit (02-30), so access to the components of the memory area can only be obtained from the procedures IsPort
, NewPort
, Send
. Outside this block, PortName
nothing is known about the variable , so access to the field of memory that stores the port state is unlikely (of course, the name can be guessed, but to encapsulate such a mechanism is quite enough).NewPort
(05-11). In addition to returning the memory area of the port itself, the argument returns, through its first argument, the future of the variable that initializes the cell. This is used to initialize Given
and Taken
(36-37).Send
(17-22).Send
at the next call, first of all, it guarantees that by linking to the previous node in a variable Ms
, and in its place by placing a link to the node in an unbound variable Mr
. Further, Send
the value of the variable is formed Ms
, which is obtained in the course of unification with a list tuple, in which the sent message is in the first place (which may be unrelated), and the future in the second Mr
(again: the list is just a tuple, in the second place which can stand anything). A subsequent call Send
will do the same with the variable.Mr
, and this process will gradually turn the variable that was used to initialize the cell to NewPort
, and the future of which was returned to the outside, into a list. In other words, it Send
really sends messages to some queue.Join
(39-46).Given
and Taken
, which as a result of calls Send
gradually turn into lists. Actual parameters Join
are always future, therefore the unification operators (41-42) are executed only after linking Xs
and Ys
that occurs in Send
, turning these variables into references to tuples of the form Message | SomeFuture
. After that, the Message
components of the incoming ( Given
) and outgoing ( Taken
) streams are unified, andJoin
called for future tails of lists (A bit like quantum mechanics, is it not? Maybe Einstein did not have enough knowledge of parallel programming to build a model of the Universe with hidden variables).Given
with the help of a Send
thread will be unified in the thread (48) with the corresponding unbound variable exposed to the stream Taken
. Of course, the situation is completely symmetrical, and the names put
and get
are used for convenience. But queueserver is still asynchronous, which is useful.Source: https://habr.com/ru/post/63715/
All Articles