Продолжаем велосипедостроение
Сабж, по аналогии с немерловскими вариантами:
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
ч.т.д.
Огромное спасибо за
идею Кириллу Кирсанову
Здравствуйте, 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
Здравствуйте, 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, будет более чем очевидным
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))])) ;; /
Здравствуйте, 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).
Здравствуйте, kochetkov.vladimir, Вы писали:
KV>Такого как в немерле, или приведенном вами ниже примере — и не получится, имхо. А вообще — получился, но во-первых, весьма удручающего вида, т.к. в питоне на данный момент, нет средств, которые бы позволили вводить в его синтаксис новые конструкции
Есть практически аналог Немерле генерирующий питоновский байт код
http://www.fiber-space.de/EasyExtend/doc/EE.html
Здравствуйте, 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()
И все-таки это был велосипед
Здравствуйте, FR, Вы писали:
FR>Есть практически аналог Немерле генерирующий питоновский байт код http://www.fiber-space.de/EasyExtend/doc/EE.html
Супер!

Сам хотел привести эту ссылку, но вспомнил только, что автор постоянно тусуется на LtU — так и не нашел.