1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
|
\input texinfo @c -*-texinfo-*-
@c %**start of header
@setfilename join-python.info
@settitle join-python
@afourpaper
@documentencoding UTF-8
@documentlanguage en
@finalout
@c %**end of header
@dircategory Python
@direntry
* join-python: (join-python). Join-calculus for Python
@end direntry
@copying
Copyright @copyright{} 2014 Mattias Andrée
@quotation
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3 or
any later version published by the Free Software Foundation; with no
Invariant Sections, with no Front-Cover Texts, and with no Back-Cover
Texts. A copy of the license is included in the section entitled
``GNU Free Documentation License''.
@end quotation
@end copying
@ifnottex
@node Top
@top Join Python -- Join-calculus for Python
@insertcopying
@end ifnottex
@titlepage
@title Join Python
@subtitle Join-calculus for Python
@author by Mattias Andrée (maandree)
@page
@vskip 0pt plus 1filll
@insertcopying
@end titlepage
@contents
@menu
* Overview:: Brief overview of Join Python.
* Signals:: The signal construct.
* Fragments:: The fragment join construct.
* Join-switches:: Advanced joining techniques.
* GNU Free Documentation License:: Copying and sharing this manual.
@end menu
@node Overview
@chapter Overview
Join Python is a Python 3 library that implements join-calculus.
Join Python can be used by importing the module @code{join} in
any Python 3 program, provided that Join Python is installed.
Join Python provides the ability to write more concise code using
almost any published concurrency pattern with explicit monitor calls.
@node Signals
@chapter Signals
A signal it as function that runs asynchronously. It is put
in its own thread. To declare a signal, add the @code{@@signal}
decorator to a function definition or pass function to the
the constructor of the class @code{signal}.
@cartouche
@example
>>> from join import *
>>> import time
>>>
>>> @@signal
>>> def sig(delay, value):
>>> time.sleep(delay)
>>> print(value)
>>>
>>> sig(0.25, 'first')
>>> print('last')
last
first
@end example
@end cartouche
@cartouche
@example
>>> from join import *
>>> import time
>>>
>>> def f(delay, value):
>>> time.sleep(delay)
>>> print(value)
>>>
>>> signal(f)(0.25, 'first')
>>> print('last')
last
first
@end example
@end cartouche
As an extension to join-calculus, signals
in Join Java can be joined with the returned
value of the function can be fetched when joined.
@cartouche
@example
>>> from join import *
>>> import time
>>>
>>> def f(delay, value):
>>> time.sleep(delay)
>>> return value ** 2
>>>
>>> sig = signal(f)(0.25, 2)
>>> print('between')
>>> print(sig.join())
between
16
@end example
@end cartouche
In the next chapter fragments will be introduced.
If you want a signal without a fragment capability
use @code{@@puresignal} or @code{puresignal} instead
of @code{@@signal} or @code{signal}.
@node Fragments
@chapter Fragments
A fragment is a partial function. What this means is
that you can make functions that block until all its
fragments have returned. Signals return immediately.
Waiting for a fragment is called joining,@footnote{Yes,
it is a bit inconvenient that waiting for a thread
or signal is also called joining.} when joining when
a fragment you receive the arguments in was invoked with
and the value it returned, the latter being an extension
to join-calculus. Remember that a signal returns an
object with an argumentless method name @code{join} that
joins with the signal and returns that value the signal
function returned.
@cartouche
@example
>>> from join import *
>>>
>>> @@fragment
>>> def frag(value):
>>> return value ** 2
>>>
>>> def f(value):
>>> ((frag_value,), _kwargs, rc) = join(frag)
>>> return rc + frag_value + value
>>>
>>> fragment(2)
>>> print(f(3))
10
@end example
@end cartouche
The function @code{join} returns a tuple of the positional
arguments, the named arguments and the returned value.
But you can also join with multiple fragemnts, in which case
@code{join} returns a list of these tuples, one tuple for
each fragment, in the same order as they appear as arguments
for the @code{join} call.
@cartouche
@example
>>> from join import *
>>>
>>> @@fragment
>>> def f1(value):
>>> return value ** 2
>>>
>>> @@fragment
>>> def f2(value):
>>> return value ** 3
>>>
>>> def f(value):
>>> ((_args1, _kwargs1, rc1), (_args2, _kwargs2, rc2)) = join(f1, f2)
>>> return value + rc1 + rc2
>>>
>>> f1(2)
>>> f2(2)
>>> print(f(2))
14
@end example
@end cartouche
As with @code{@@signals}, you can use constructor of @code{fragment}
instead of a the @code{@@fragment} decorator.
@node Join-switches
@chapter Join-switches
Join-switches is an advanced joining technique. It lets join
join with whatever fragment group that is joined first. There
are two versions of this: ordered joining and unordered joining.
Ordered joining and unordered joining behaves the same if none
of the fragment group have already joined when the joining is
requested. But if there are more than one fragment group that
have already joined that will behave differently: unordered
joining will select a fragment group at random, whereas ordered
joining will select the fragment group with highest precedence,
that is, the fragment group specified first in the joining
request. Ordered joining is done with the function
@code{ordered_join} and unordered joining is done with the
function @code{unordered_join}. They work similarly to @code{join},
except each group is specified as a tuple of fragments and
the returned value is a tuple of the index of the selected
join case (fragment group) and what @code{join} returned for
that fragment group.
@cartouche
@example
>>> from join import *
>>>
>>> @@fragment
>>> def f1():
>>> pass
>>>
>>> @@fragment
>>> def f2():
>>> pass
>>>
>>> @@fragment
>>> def f3():
>>> pass
>>>
>>> def ordered():
>>> (case, (_jargs, _jkwargs, _jrc)) = ordered_join((f1,), (f2,), (f3,))
>>> return case
>>>
>>> def unordered():
>>> (case, (_jargs, _jkwargs, _jrc)) = unordered_join((f1,), (f2,), (f3,))
>>> return case
>>>
>>> def switch(f):
>>> f1()
>>> f2()
>>> f3()
>>> return f()
>>>
>>> print([switch(ordered) for _ in range(10)])
>>> print([switch(unordered) for _ in range(10)])
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[2, 2, 1, 0, 1, 2, 0, 1, 1, 2] # uniformally random
@end example
@end cartouche
@node GNU Free Documentation License
@appendix GNU Free Documentation License
@include fdl.texinfo
@bye
|