[python] вариантные типы данных
От: kochetkov.vladimir Россия https://kochetkov.github.io
Дата: 12.10.09 17:14
Оценка: 49 (2)
Продолжаем велосипедостроение

Сабж, по аналогии с немерловскими вариантами:

pyvariants.py:
import sys

_typeDefinitions = {}

def _variantsInit(self, *args, **kwargs):
    i = 0
    for arg in _typeDefinitions[self.__class__.__name__.split('.')[-1]]:
        if kwargs.has_key(arg):
            self.__dict__[arg] = kwargs[arg]
            del kwargs[arg]
        else:
            self.__dict__[arg] = args[i]
            i += 1

class _VariantMcs(type):
    def __new__(mcs, name, bases, dct):
        if 'variants' in dct:
            if type(bases) == type(""): bases = (bases,)
            for typeName, typeArgs in dct['variants'].iteritems():
                if type(typeArgs) == type(""): typeArgs = (typeArgs,)
                _typeDefinitions[typeName] = typeArgs
        return type.__new__(mcs, name, bases, dct)

class VariantBase:
    __metaclass__ = _VariantMcs

    def __init__(self):
        for typeName, typeArgs in _typeDefinitions.iteritems():
            setattr(sys.modules[self.__module__], typeName, type(typeName, (type(self),), {'__init__': _variantsInit}))


Будем использовать так, как VladD2 учил
Автор(ы): Чистяков Влад (VladD2)
Дата: 03.03.2007
Язык программирования Nemerle заинтересовал многих в первую очередь своей мощнейшей подсистемой мак-росов. Однако и без них Nemerle предоставляет ряд су-щественных улучшений по сравнению с традиционными, императивными языками программирования (такими как Java, C# и C++).
Nemerle, кроме традиционного императивного програм-мирования, поддерживает функциональное программи-рование. Это выражается в наличии конструкций, упро-щающих манипуляцию функциями, построение и анализ сложных структур данных и т.п.
К сожалению, если вы не использовали возможности, присущие функциональным языкам ранее, то вам будет трудно оценить, насколько Nemerle может оказаться вам полезным в реальной повседневной работе. Данная статья призвана в неформальной форме продемонс-трировать это.
, т.е. построим интерпретатор арифметических операций (все операции реализовывать было лень, если что Вот появится в питоне паттерн-матчинг, тогда и реализую все сразу. Впрочем, это уже тема следующего топика ):

from pyvariants import VariantBase

class Expr(VariantBase):
    variants={
        'Literal'   : ('value'),
        'Plus'      : ('expr1', 'expr2'),
        'Minus'     : ('expr1', 'expr2'),
    }

    def __str__(self):
        typeName = self.__class__.__name__
        return \
            typeName == 'Literal' and str(self.value) or \
            typeName == 'Plus'    and str(self.expr1) + ' + ' + str(self.expr2) or \
            typeName == 'Minus'   and str(self.expr1) + ' - ' + str(self.expr2)

    def eval(self):
        typeName = self.__class__.__name__
        return \
            typeName == 'Literal' and self.value or \
            typeName == 'Plus'    and self.expr1.eval() + self.expr2.eval() or \
            typeName == 'Minus'   and self.expr1.eval() - self.expr2.eval()


Expr()

expr = Plus(Literal(10), Literal(15))

print expr, '=', expr.eval()


имеем:

10 + 15 = 25



ч.т.д.

Огромное спасибо за идею Кириллу Кирсанову

[Интервью] .NET Security — это просто
Автор: kochetkov.vladimir
Дата: 07.11.17
Re: [python] вариантные типы данных
От: z00n  
Дата: 13.10.09 02:10
Оценка: 1 (1)
Здравствуйте, kochetkov.vladimir, Вы писали:

KV>Продолжаем велосипедостроение


А чем это лучше, чем:
def evl(expr):
    if   expr[0] == 'Plus':
        return evl(expr[1]) + evl(expr[2])
    elif expr[0] == 'Mul':
        return evl(expr[1]) * evl(expr[2])
    elif expr[0] == 'Literal':
        return expr[1]

# TEST:
print evl(["Plus",["Literal",1],["Literal",41]]) #=> 42
Re[2]: [python] вариантные типы данных
От: kochetkov.vladimir Россия https://kochetkov.github.io
Дата: 13.10.09 16:18
Оценка:
Здравствуйте, z00n, Вы писали:

Z>А чем это лучше, чем:

Z>
Z>def evl(expr):
Z>    if   expr[0] == 'Plus':
Z>        return evl(expr[1]) + evl(expr[2])
Z>    elif expr[0] == 'Mul':
Z>        return evl(expr[1]) * evl(expr[2])
Z>    elif expr[0] == 'Literal':
Z>        return expr[1]

Z># TEST:
Z>print evl(["Plus",["Literal",1],["Literal",41]]) #=> 42
Z>


Оно лучше примерно тем же, чем лучше приведенного тобой примера следующий код:

print 41+1




От задачи таки-зависит. Если нам надо сделать пару операций над парой чисел, то ничем оно не лучше, IMHO. А если есть необходимость работать с "развесистыми иерархиями" (с), преобразовывать их, интерпретировать, осуществляя синтаксический разбор чего-либо, гуляя по файловой системе или реестру, выстраивая модель, полученную из БД или еще откуда-нибудь и т.п., то использование подобных типов позволяет делать это компактно и быстро. Мне не хотелось загромождать сообщение какими-нибудь сложными примерами, поэтому и привел первое, что пришло на ум.

Если хочется сложнее, то вот полная версия интерпретатора арифметических выражений из статьи Влада:

from pyvariants import VariantBase
from string import replace

def do_call(name, parms):
    if name in calls_example:
        return calls_example[name](*parms)
    else:
        raise NotImplementedError, '%s(%s) is not implemented' % name, parms

calls_example = {
    #function name    implementation
    'min'           : lambda *x: max(x),
    'max'           : lambda *x: min(x),
}

grammar_example = {
    #AST token    arguments             string representation       delegate
    'Call'      : [('name', 'parms'),   '%(name)s%(parms)s',        lambda **x: do_call(x['name'], x['parms'])],
    'Mul'       : [('expr1', 'expr2'),  '(%(expr1)s) * %(expr2)s',  lambda **x: x['expr1'] * x['expr2']],
    'Div'       : [('expr1', 'expr2'),  '(%(expr1)s) / %(expr2)s',  lambda **x: x['expr1'] / x['expr2']],
    'Plus'      : [('expr1', 'expr2'),  '%(expr1)s + %(expr2)s',    lambda **x: x['expr1'] + x['expr2']],
    'Minus'     : [('expr1', 'expr2'),  '%(expr1)s - %(expr2)s',    lambda **x: x['expr1'] - x['expr2']],
    'Literal'   : [('value',),          '%(value)s',                lambda **x: x['value']]
}

class Expr(VariantBase):

    grammar = grammar_example
    variants = dict(map(lambda item: [item[0], item[1][0]], grammar.iteritems()))

    def _evaluator(self, f):
        return dict(map(lambda arg:
            [arg,
            f(getattr(self, arg)) if isinstance(getattr(self, arg), Expr) else
                  tuple(map(lambda x: f(x), getattr(self, arg))) if isinstance(getattr(self, arg), tuple) else
                      getattr(self, arg)
            ],
            self.__dict__))

    def __str__(self):
        return replace(self.__class__.grammar[self.__class__.__name__][1] % self._evaluator(lambda x: str(x)), '\'', '')

    def eval(self):
        return self.__class__.grammar[self.__class__.__name__][2](**self._evaluator(lambda x: x.eval()))

Expr()

expr = Mul(Plus(Call('min', (Literal(6), Literal(4))), Literal(32)), Literal(0.2))
result = expr.eval()
print '%s = %s' % (expr, result)


Обрати внимание, чтобы добавить новую операцию или функцию мне достаточно вставить всего одну строчку в описание грамматики или вызовов (причем нет особых проблем с тем, чтобы они были прописаны не прямо в классе, как в примере, а где-нибудь снаружи). Чтобы реализовать какое-нибудь единое поведение при выполнении любых операций (логирование, выброс исключений, не специфичных для конкретного типа и т.п.) — достаточно добавить один метод в класс Expr — все варианты его унаследуют, равно как и все остальные его атрибуты.

Если ты сам изобразишь приведенный выше код (реализовав все его возможности, разумеется) с использованием предложенного тобой подхода, то ответ на вопрос "чем лучше", IMHO, будет более чем очевидным

[Интервью] .NET Security — это просто
Автор: kochetkov.vladimir
Дата: 07.11.17
Re[3]: [python] вариантные типы данных
От: z00n  
Дата: 14.10.09 02:41
Оценка: 52 (2)
KV>Оно лучше примерно тем же, чем лучше приведенного тобой примера следующий код:

OK, я разверну. Вариантный тип должен(обязан) энфорсить контракт на типы аргументов конструкторов. В Nemerle (Haskell, ML etc.) это очевидно так (статическая типизация и алгебраические типы).

В вашем VariantBase это очевидно не так:
>>> expr = Plus(666,"FooBar")  # must be error
>>> expr.expr1
666
>>> expr.expr2
'FooBar'


Без этой важной детали городить огород бессмысленно — вы не получаете никаких существенных преимуществ перед "сырыми" структурами дынных, или простым наследованием. Паттерн-матчинга в результате тоже не получилось.

Вот хороший пример, того, как вариантный тип данных должен выглядеть в языке с динамической типизацией (Scheme). Во всех конструкторах (num,add,sub) указан предикат типа аргумента, предикат AE? генерится автоматически макросом define-type.
;; Пример с 6-ой страницы PLAI (AE - сокращенно от ArithmeticExpression) 
;; Обратите внимание на предикаты 'AE?' и 'number?'
(define-type AE
  [num (n number?)]
  [add (lhs AE?)
       (rhs AE?)]
  [sub (lhs AE?)
       (rhs AE?)])


Использование:
(define (calc an-ae)
  (type-case AE an-ae  ;; <-- проверка правильности типа an-ae
    [num (n) n]                                ;; \
    [add (l r) (+ (calc l) (calc r))]          ;; | <- pattern-matching
    [sub (l r) (- (calc l) (calc r))]))        ;; /
Re[4]: [python] вариантные типы данных
От: kochetkov.vladimir Россия https://kochetkov.github.io
Дата: 14.10.09 09:24
Оценка:
Здравствуйте, z00n, Вы писали:

KV>>Оно лучше примерно тем же, чем лучше приведенного тобой примера следующий код:


Z>OK, я разверну. Вариантный тип должен(обязан) энфорсить контракт на типы аргументов конструкторов. В Nemerle (Haskell, ML etc.) это очевидно так (статическая типизация и алгебраические типы).


А, теперь вопрос понял

Z>В вашем VariantBase это очевидно не так:

Z>
>>>> expr = Plus(666,"FooBar")  # must be error
>>>> expr.expr1
Z>666
>>>> expr.expr2
Z>'FooBar'
Z>


Но это же proof-of-concept, а не законченный код. Встроить в _variantsInit проверку на соответствие аргументов заявленному контракту, который бы декларировался в variants вполне реально.

Z>Без этой важной детали городить огород бессмысленно — вы не получаете никаких существенных преимуществ перед "сырыми" структурами дынных, или простым наследованием.


Насчет структур, готов поспорить (это из разряда procedural vs oop ), а поводу наследования — соглашусь, т.к. перед ним действительно нет никаких преимуществ, потому что это наследование и есть, только осуществляется оно не руками разработчика, а кодогенерацией на основе деклараций в variants.

Кстати, именно так разворачивает варианты компилятор немерла, насколько я помню. Если упрощенно, то на каждый из "конструкторов" варианта им создается класс-наследник от варианта с конструктором, соответствующим контракту, и членами, реализующими этот контракт. Разница по сравнению с обсуждаемым кодом лишь в том, что немерле это подклассы генерируются внутри варианта, а у меня — в модуле, где был инстанцирован вариант (для удобства отладки, вообще-то, а так — не принципиально)

Z> Паттерн-матчинга в результате тоже не получилось.


Такого как в немерле, или приведенном вами ниже примере — и не получится, имхо. А вообще — получился, но во-первых, весьма удручающего вида, т.к. в питоне на данный момент, нет средств, которые бы позволили вводить в его синтаксис новые конструкции (за исключением хаков с sys.trace, что для продуктивного кода неприемлемо), чтобы обеспечить удобочитаемость гипотетических сопоставлений, а во-вторых, реализовать нетривиальные сопоставления — та еще задача в плане сложности и написания объема необходимого кода. Поэтому и заменил его на функции, работающие с grammar_example. Если все-таки получится реализовать его в более-менее читабельном виде, тогда закину сюда, обсудим.

Z>Вот хороший пример, того, как вариантный тип данных должен выглядеть в языке с динамической типизацией (Scheme).



[Интервью] .NET Security — это просто
Автор: kochetkov.vladimir
Дата: 07.11.17
Re[5]: [python] вариантные типы данных
От: FR  
Дата: 15.10.09 15:06
Оценка: 34 (2)
Здравствуйте, kochetkov.vladimir, Вы писали:


KV>Такого как в немерле, или приведенном вами ниже примере — и не получится, имхо. А вообще — получился, но во-первых, весьма удручающего вида, т.к. в питоне на данный момент, нет средств, которые бы позволили вводить в его синтаксис новые конструкции


Есть практически аналог Немерле генерирующий питоновский байт код http://www.fiber-space.de/EasyExtend/doc/EE.html
Re[6]: [python] вариантные типы данных
От: kochetkov.vladimir Россия https://kochetkov.github.io
Дата: 15.10.09 15:52
Оценка: :)
Здравствуйте, FR, Вы писали:

FR>Здравствуйте, kochetkov.vladimir, Вы писали:



KV>>Такого как в немерле, или приведенном вами ниже примере — и не получится, имхо. А вообще — получился, но во-первых, весьма удручающего вида, т.к. в питоне на данный момент, нет средств, которые бы позволили вводить в его синтаксис новые конструкции


FR>Есть практически аналог Немерле генерирующий питоновский байт код http://www.fiber-space.de/EasyExtend/doc/EE.html


Оттуда:

class Wildcard(object):
    def __eq__(self, other):
        return True
   
    def __ne__(self, other):
        return False

wildcard = Wildcard()


И все-таки это был велосипед

[Интервью] .NET Security — это просто
Автор: kochetkov.vladimir
Дата: 07.11.17
Re[6]: [python] вариантные типы данных
От: z00n  
Дата: 15.10.09 22:09
Оценка:
Здравствуйте, FR, Вы писали:

FR>Есть практически аналог Немерле генерирующий питоновский байт код http://www.fiber-space.de/EasyExtend/doc/EE.html


Супер! Сам хотел привести эту ссылку, но вспомнил только, что автор постоянно тусуется на LtU — так и не нашел.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.