!= акторы) do-синтаксис на генераторахcall/cc, зелёные нитиПродолжение — состояние потока управления, доступное через конструкции языка.
Сопроцедура — процедура с несколькими точками входа и выхода с сохранением состояния, разновидность продолжений.
Генератор — сопроцедура, которая только возвращает значения.
goto
returnfor, while, continue, breakcall/cc (Scheme, Ruby, Haskell: монада Cont)ByteString -> a, a -> ByteStringВначале итераторы, интерфейс генераторов с внешним миром.
class Iterable(object):
def __iter__(self):
'Iterable(a) -> Iterator(a)'
class Iterator(Iterable):
def next(self):
'Iterator(a) -> a'
Abstract Base Classes (ABC), см. PEP 3119.
for и порождения списковЭти конструкции выполняют протокол итератора.
list = [1, 2, 3]
for x in list:
print x
# Ленивый range
iterable = xrange(1000000)
for x in iterable:
print x
sq_of_odds = [square(x) for x in iterable
if odd(x)]
print '\n'.join(sq_of_odds)
iter
Рассмотрим iter как пример реализации итератора ручками и на генераторах. Он нужен для перебора значений процедуры с эффектами. Использование iter:
from md5 import md5
def md5_update(md, data):
md.update(data)
return md
fd = open(filename 'rb')
contents = iter(lambda: fd.read(block_size), '')
md = reduce(md5_update, contents, md5())
print md.hexdigest()
iterИнтересен iter с 2 аргументами для перебора значений процедуры с эффектами.
def iter(*args):
'Iterable(a) -> Iterator(a)'
'(None -> Eq(a)), Eq(a) -> Iterator(a)'
if len(args) == 1:
collection, = args
return collection.__iter__()
elif len(args) == 2:
callable, sentinel = args
return _Iter(callable, sentinel)
else:
raise TypeError('...')
_Iter как класс
class _Iter(object):
def __init__(self, callable, sentinel):
self._callable = callable
self._sentinel = sentinel
def __iter__(self):
return self
def next(self):
res = self._callable()
if res == self._sentinel:
raise StopIteration()
return res
assert isinstance(_Iter(...), Iterator)
_Iter как генераторСинтаксис генераторов: yield.
def _Iter(callable, sentinel):
while True:
res = callable()
if res == sentinel:
raise StopIteration()
yield res
assert isinstance(_Iter(...), Iterator)
itertools
def chain(*iterables):
for it in iterables:
for element in it:
yield element
def count(n=0):
while True:
yield n
n += 1
sq_of_odds = (square(x) for x in iterable if odd(x))
Ср. с порождениями списков:
sq_of_odds = [square(x) for x in iterable if odd(x)]
Iterable(a), который реализован в виде генератора, осуществляющего эффекты чтения)Iterable(a) в код, который осуществляет эффекты записи
f: environ, start_response -> response
{bytes: object},
(bytes, [(bytes, bytes)] -> (bytes -> None)) ->
Iterable(bytes)
Можно думать о нём как о:
f: environ, request -> status, headers, response
{bytes: object}, Iterable(bytes) ->
bytes, [(bytes, bytes)], Iterable(bytes)
def fileread(environ, start_response):
start_response('200 OK', [...])
with open(filename, 'rb') as fd:
for block in iter(lambda: fd.read(block_size), ''):
yield block
def upper(app):
def mid(environ, start_response):
return (s.upper() for s in app(environ, start_response))
return mid
app = upper(fileread)
В Python 2.5: передача значений внутрь генератора, сопроцедуры, см. PEP 342. Пример сопроцедурного планировщика — при обсуждении акторов.
class Generator(Iterator):
def send(self, value):
'Generator(a), a -> a'
def throw(self, type, value=None, traceback=None):
pass
Аргумент send становится значением yield.
def dialogue():
yield append_chan(stdout, 'enter filename\n')
name = yield read_chan(stdin)
yield append_chan(stdout, name)
try:
contents = read_file(name)
except IOError:
yield append_chan(stdout, "can't open file")
else:
yield append_chan(stdout, contents)
Интерфейс монад, затем do-синтаксис на генераторах. Класс Monad в виде интерфейса OOP:
class Monad(object):
def bind(self, f):
'Monad(a), (a -> Monad(b)) -> Monad(b)'
raise NotImplementedError()
@staticmethod
def unit(value):
'a -> Monad(a)'
raise NotImplementedError()
Можно было и через ABC.
Maybe (1)
class Maybe(Monad):
def bind(self, f):
'Maybe(a), (a -> Maybe(b)) -> Maybe(b)'
if isinstance(self, Nothing):
return self
else:
return f(self.value)
@staticmethod
def unit(value):
'a -> Maybe(a)'
return Just(value)
Аналогично методы MonadPlus.
Maybe (2)
class Just(Maybe):
def __init__(self, value):
self.value = value
def __repr__(self):
return 'Just(%r)' % self.value
class Nothing(Maybe):
def __repr__(self):
return 'Nothing()'
В Scala и C# это были бы case or final classes.
Monad
def msum(x, y):
'Monad(int), Monad(int) -> Monad(int)'
return (
x.bind(lambda x_:
y.bind(lambda y_:
type(x).unit(x_ + y_))))
do-синтаксисомВ этом сахаре = yield — аналог <-, а mreturn — return.
@do
def msum(x, y):
'(Monad(int), Monad(int)) -> Generator(Monad(int))'
x_ = yield x
y_ = yield y
mreturn(x_ + y_)
Декораторы: @do — это foo = do(foo).
do-синтаксис для монад (1)На основе Monads in Python (with nice syntax). Используются двусторонние генераторы:
def do(f):
'(*, ** -> Generator(Monad(a))) -> (*, ** -> Monad(a))'
def wrapper(*args, **kwargs):
'*, ** -> Monad(a)'
gen = f(*args, **kwargs)
first = gen.next()
def send(value):
'a -> Monad(a)'
try:
m = gen.send(value)
return m.bind(send)
except MonadReturn, ret:
return type(first).unit(ret.value)
return first.bind(send)
return wrapper
do-синтаксис для монад (2)
class MonadReturn(Exception):
def __init__(self, value):
Exception.__init__(self)
self.value = value
def mreturn(value):
raise MonadReturn(value)
call/cc
call/cc — продолжения как полноправные объекты.
(+ 1 (call/cc
(lambda (k)
(+ 2 (k 3)))))
(+1 [])
k — остальная программа в момент call/cc, дырка [] в вычислении. Вызов продолжения покидает текущий контекст. См. Fixnum Days. Chapter 13.
break на call/ccДругие продолжения можно выразить через call/cc:
(call/cc
(lambda (break)
(for-each (lambda (x)
(if (negative? x)
(break x)))
'(54 0 37 -3 245 19))
'()))
call/cc и нечитаемый кодcall/cc можно реализовать многие типы продолжений, если не всеcall/cc ведёт к очень запутанному коду, сложно следить за потоком управленияcall/cc, генераторов и т. д. не схватывает сути взаимодействия — асинхронного обмена сообщениямиcall/cc и т. д.
Источник: C-Workshop
Источник: T/TCP — TCP for Transactions
threadingQueue
def ping(pong, count):
pid = yield
for i in xrange(count):
pong << (pid, i)
msg = yield
print 'ping: got %r' % msg
pong << (pid, Terminate())
def pong():
pid = yield
while True:
sender, msg = yield
if isinstance(msg, Terminate):
break
else:
print 'pong: got %r' % msg
sender << 'pong back %r' % msg
sched = SimpleScheduler() # or ThreadedScheduler()
o = sched.spawn(pong())
sched.spawn(ping(o, 10))
sched.start()
sched.stop()
class SimpleScheduler(Scheduler):
def __init__(self):
self.msgs = []
def send(self, receiver, msg):
self.msgs.append((receiver, msg))
def spawn(self, g):
g_ = Pid(self, g)
g_.next()
g_.send(g_)
return g_
def start(self):
while len(self.msgs) > 0:
receiver, msg = self.msgs.pop(0)
try:
receiver.send(msg)
except StopIteration: pass
def stop(self): pass
forkIOChanforkOSChanjoin нитейChan
data Ping = Ping String | Terminate
data Pong = Pong String
pong :: Actor (Pid Pong, Ping) -> IO ()
pong self = do
(from, msg) <- receive self
case msg of
Ping str -> do putStrLn $ "pong: got " ++ str
from ! Pong ("pong back " ++ str)
pong self
Terminate -> return ()
ping :: Pid (Pid Pong, Ping) -> Integer -> Actor Pong -> IO ()
ping pong 0 self = do
pong ! (getPid self, Terminate)
ping pong count self = do
pong ! (getPid self, Ping (show count))
msg <- receive self
case msg of
Pong str -> do putStrLn $ "ping: got " ++ str
ping pong (count - 1) self
main = do
o <- spawn pong
i <- spawn $ ping (getPid o) 10
join i
join o
data Actor a = Actor {
actorChan :: Chan a,
actorMVar :: MVar ()
}
type Pid a = a -> IO ()
send :: Pid a -> a -> IO ()
send pid = pid
(!) = send
receive :: Actor a -> IO a
receive a = readChan $ actorChan a
Хак с MVar, т. к. нет join.
spawn :: (Actor a -> IO ()) -> IO (Actor a)
spawn f = do
m <- newEmptyMVar
c <- newChan
let a = Actor c m in do
forkIO ((f a) `finally` putMVar m ())
return a
join :: Actor a -> IO ()
join a = takeMVar $ actorMVar a
getPid :: Actor a -> Pid a
getPid a = writeChan $ actorChan a
Пока нет newActor для обменов сообщениями вне очереди актора.
call/ccmake-async-channel
(define (async-sleeper self)
(let ((msg (recv self)))
(if (message? msg)
(let ((x (message-body msg)))
(cond ((number? x)
(begin
(sleep x)
(send (message-from msg) '())
(async-sleeper self)))
((and (symbol? x) (eq? x 'done))
(begin
(display "sleep: done\n")
'()))
(else (async-sleeper self))))
(async-sleeper self))))
В стиле Erlang. Выглядят как функции, что не очень хорошо (см. RPC). Надо бы свой синтаксис.
(define (make-sleeper)
(spawn async-sleeper))
(define (sleeper-sleep s count)
(let ((self (new-pid)))
(send s (cons self count))
(recv self)))
(define (sleeper-stop s)
(let ((self (new-pid)))
(send s (cons self 'done))
(join s)))
На акторах весьма естественно выражаются следующие задачи:
aio_read)select или poll в одной нити(Хорошо бы показать диаграммы)
Можно представить потенциальный язык:
ioctl и др. syscalls (shmget не вспоминаем)