1. Генераторы и акторы

    Андрей Власовских

    © 2008. Доступно по Creative Commons

  2. Интересные ссылки

  3. План

    1. Генераторы
      • Виды продолжений
      • Генераторы в Python
      • Монадический do-синтаксис на генераторах
      • call/cc, зелёные нити
    2. Акторы
      • Акторы как подход к эффектам и параллельности
      • Реализация на Python, Scheme, Haskell
      • Интерфейсы IO
  4. Словарик

  5. Генераторы

  6. Определения

    Продолжение — состояние потока управления, доступное через конструкции языка.

    Сопроцедура — процедура с несколькими точками входа и выхода с сохранением состояния, разновидность продолжений.

    Генератор — сопроцедура, которая только возвращает значения.

  7. Виды продолжений

  8. Генераторы в Python

  9. Интерфейс итератора

    Вначале итераторы, интерфейс генераторов с внешним миром.

              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.

  10. Протокол итератора

  11. 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)
    
            
  12. 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()
    
            
  13. Реализация 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('...')
    
            
  14. _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)
    
            
  15. _Iter как генератор

    Синтаксис генераторов: yield.

              def _Iter(callable, sentinel):
        while True:
            res = callable()
            if res == sentinel:
                raise StopIteration()
            yield res
    
    assert isinstance(_Iter(...), Iterator)
    
            
  16. Примеры из itertools

               def chain(*iterables):
         for it in iterables:
             for element in it:
                 yield element
    
     def count(n=0):
         while True:
             yield n
             n += 1
    
            
  17. Порождения генераторов

              sq_of_odds = (square(x) for x in iterable if odd(x))
    
            

    Ср. с порождениями списков:

              sq_of_odds = [square(x) for x in iterable if odd(x)]
    
            
  18. Генераторы для локализации IO

  19. Интерфейс WSGI

              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)
    
            
  20. WSGI apps and middleware

              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)
    
            
  21. Двусторонние генераторы

    В 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.

  22. Аналог stream-based IO в Haskell

              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)
    
            
  23. Монады в Python

    Интерфейс монад, затем 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.

  24. 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.

  25. 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.

  26. Сумма значений 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_))))
    
            
  27. Сумма с do-синтаксисом

    В этом сахаре = yield — аналог <-, а mreturnreturn.

              @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).

  28. 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
    
            
  29. do-синтаксис для монад (2)

              class MonadReturn(Exception):
        def __init__(self, value):
            Exception.__init__(self)
            self.value = value
    
    def mreturn(value):
        raise MonadReturn(value)
    
            
  30. call/cc

    call/cc — продолжения как полноправные объекты.

              (+ 1 (call/cc
           (lambda (k)
             (+ 2 (k 3)))))
    
    (+1 [])
    
            

    k — остальная программа в момент call/cc, дырка [] в вычислении. Вызов продолжения покидает текущий контекст. См. Fixnum Days. Chapter 13.

  31. break на call/cc

    Другие продолжения можно выразить через call/cc:

              (call/cc
      (lambda (break)
        (for-each (lambda (x)
                    (if (negative? x)
                        (break x)))
                  '(54 0 37 -3 245 19))
        '()))
    
            
  32. call/cc и нечитаемый код

  33. Продолжения и параллельность

  34. Акторы

  35. Кратко об истории

  36. Взгляды на акторов

  37. Мир акторов

  38. Настоящее OOP

  39. Почти общий случай в сетевых протоколах

  40. Автомат протокола TCP

    Источник: C-Workshop

  41. Пример последовательности TCP

    Источник: T/TCP — TCP for Transactions

  42. Акторы на разных языках

  43. Акторы на Python

  44. Ping pong на Python (1)

              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
    
            
  45. Ping pong на Python (2)

              sched = SimpleScheduler() # or ThreadedScheduler()
    o = sched.spawn(pong())
    sched.spawn(ping(o, 10))
    sched.start()
    sched.stop()
    
            
  46. Сопроцедурный планировщик на Python

              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
    
            
  47. Акторы на Haskell

  48. Ping pong на Haskell (1)

              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 ()
    
            
  49. Ping pong на Haskell (2)

              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
    
            
  50. Реализация на Haskell (1)

              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.

  51. Реализация на Haskell (2)

              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 для обменов сообщениями вне очереди актора.

  52. Акторы на Scheme

  53. Таймер на Scheme

              (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))))
    
            
  54. Интерфейсные функции на Scheme

    В стиле 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)))
    
            
  55. Хорошо выразить на акторах

    На акторах весьма естественно выражаются следующие задачи:

    (Хорошо бы показать диаграммы)

  56. Пара идей

  57. Язык с IO через акторов

    Можно представить потенциальный язык:

  58. Интерфейс программ с внешним миром