Комментарии 18
Основное, что я понял из этой статьи — изучение Хаскеля нешуточно выворачивает мозг.
Надо брать!
Надо брать!
Мне кажется, что инфиксабели красивее будет изобразить в виде слегка-монад.
Тоесть — саму инфиксную функцию сделать типа-монадической, с автомагическим поглащением не типа-монадических значений в цепочке.
(префиксы слегка- и типа- означают, что я пока не дорос до осознания граинц применения этих терминов)
Получается весьма красиво:
add = Infixable(lambda x,y: x+y)
sum = 1 |add| 2 |add| 3 # превращается в Infixable(1) | add | Infixable(2) | add | Infixable(3)
sum.val = 6
Разумеется, возникает проблема вывода значения из такой слегка-монады.
Но можно попробовать намутить что-нибудь с __coerce__
Тоесть — саму инфиксную функцию сделать типа-монадической, с автомагическим поглащением не типа-монадических значений в цепочке.
(префиксы слегка- и типа- означают, что я пока не дорос до осознания граинц применения этих терминов)
Получается весьма красиво:
add = Infixable(lambda x,y: x+y)
sum = 1 |add| 2 |add| 3 # превращается в Infixable(1) | add | Infixable(2) | add | Infixable(3)
sum.val = 6
Разумеется, возникает проблема вывода значения из такой слегка-монады.
Но можно попробовать намутить что-нибудь с __coerce__
class Infixable(object): def __init__(self, arg): if callable(arg): self.fn = arg else: self.val = arg def __repr__(self): if hasattr(self,'val'): return '<Inf=%s>'%repr(self.val) else: return "<Inf(%d)>"%self.fn.func_code.co_argcount def __or__(self, other): # self | other if not isinstance(other,Infixable): # self | noninf other = Infixable(other) logging.debug("%s + %s",repr(self),repr(other)) if hasattr(self,'val') and hasattr(other,'fn'): # val | fn -> fn=curry return Infixable(lambda x: other.fn(self.val,x)) elif hasattr(self,'fn') and hasattr(other,'val'): # fn | val -> val=fn(val) return Infixable(self.fn(other.val)) else: raise ValueError("bogus operands in chain") def __ror__(self,other): # noninf | self return Infixable(other) | self
Можно и так. Но перегрузка операторов имеет одно неприятное свойство: не всегда прогнозируемый порядок вызова, а это как раз минус: в монадном контексте порядок вычисления должен быть строго последовательным.
Я тоже сначала сделал перегрузку >>, |, *, и вот тут то меня и подвел приоритет операций. В вашем варианте перегружен только пайп, но даже он уже может быть как лево-, так и правосторонним.
Я тоже сначала сделал перегрузку >>, |, *, и вот тут то меня и подвел приоритет операций. В вашем варианте перегружен только пайп, но даже он уже может быть как лево-, так и правосторонним.
Я тут подумал, что пока пишешь сам и для себя — это, вероятно, всё очень интересно. Правда на выходе получаем какого-то уродца с нешуточной магией внутри. Мне интересно какая была бы реакция у моих коллег, увидев бы они такое… вот покажу им эту статью на днях, обсудим.
Что-то мне подсказывает, в реальном мире не существует ни одной реальной задачи, где бы подобная магия была бы уместна. Разве что, как экзотическая зарядка для ума…
Это только одна монада из многих, а они очень разные.
Самые разные монады напрямую используются в функциональных языках. Некоторые реализованы и для императивных языков.
Если объект имеет метод, который возвращает сам объект, мы можем писать так:
Это тоже частный случай монады, просто он более привычен людям, привыкшим к ООП.
Самые разные монады напрямую используются в функциональных языках. Некоторые реализованы и для императивных языков.
Если объект имеет метод, который возвращает сам объект, мы можем писать так:
some_obj.set_name("abc").inrease_age().update_items([1,2,3]).validate().save()
Это тоже частный случай монады, просто он более привычен людям, привыкшим к ООП.
> Если объект имеет метод, который возвращает сам объект, мы можем писать так:
> some_obj.set_name(«abc»).inrease_age().update_items([1,2,3]).validate().save()
>
> Это тоже частный случай монады, просто он более привычен людям, привыкшим к ООП.
Ох, ну классно! У нас, оказывается, повсюду монады :)
Вот всегда так в питоне. Берешь утюг и гладишь. А оказывается, это высокотехнологичный прибор с парогенератором низкого давления и прецизионным термостатом и некоторые диссертации на эту тему защищают.
> some_obj.set_name(«abc»).inrease_age().update_items([1,2,3]).validate().save()
>
> Это тоже частный случай монады, просто он более привычен людям, привыкшим к ООП.
Ох, ну классно! У нас, оказывается, повсюду монады :)
Вот всегда так в питоне. Берешь утюг и гладишь. А оказывается, это высокотехнологичный прибор с парогенератором низкого давления и прецизионным термостатом и некоторые диссертации на эту тему защищают.
Функция, которая принимает обычное значение и возвращает результат в контексте (монадное значение), называется монадной функцией
не как могу сообразить, что к чему в примере с конями. в строчке:
List(pos) +'>>='+ raw_jumps +'>>='+ if_valid
функция raw_jumps
используется совместно с оператором '>=='
из монады List
, значит она должна быть монадической. по определению, функция
raw_jumps
должна принимать обычное значение координат, и возвращать результат в контексте (т.е. List(result)). но если посмотреть код выше, то функция определена иначе: raw_jumps = lambda (x, y): [ ... ]
т.е. принимая координаты, функция возвращает объект встроенного типа list, который к монаде List не имеет отношения. или имеет? в чем магия?В целом, Вы правы — нужно бы возвращать объект класса List. Но данная реализация монад несколько своеобразна. Настоящее монадное значение — immutable. И после каждого >>= или >> мы получаем новое значение. Но конкретная реализация монад — это последовательность изменений первого контейнера в цепочке, что можно увидеть по коду методов applicate/bind/then. Это волевое решение: мне, в данном случае, показалась более удобной именно такая реализация — всё равно от нового монадного значения нужно только лишь содержимое. Начальное же значение упаковывается в экземпляр List только для реализации поведения, если бы можно было дополнить класс списка, я, безусловно, бы так и сделал. Таким образом монадные функции возвращают простой список (так их проще писать), но думать о нем нужно, как о монадном контексте.
Maybe-значение, по своей сути, тоже может быть представлено в виде простого кортежа (Bool, x). Можно было бы переписать just(), чтобы он возвращал просто кортеж. Тогда Maybe-последовательность нужно было бы начинать вручную с Maybe(just=x).
Кстати, в Haskell, в котором я и черпал вдохновение, список уже имеет монадное поведение, поэтому там монадные функции всегда возвращают список.
Спасибо большое Вам за то, что обратили внимание на несоответствие описания и кода. Можно переписать методы класса List, чтобы они принимали объекты класса List, а монадные функции их возвращали.
Maybe-значение, по своей сути, тоже может быть представлено в виде простого кортежа (Bool, x). Можно было бы переписать just(), чтобы он возвращал просто кортеж. Тогда Maybe-последовательность нужно было бы начинать вручную с Maybe(just=x).
Кстати, в Haskell, в котором я и черпал вдохновение, список уже имеет монадное поведение, поэтому там монадные функции всегда возвращают список.
Спасибо большое Вам за то, что обратили внимание на несоответствие описания и кода. Можно переписать методы класса List, чтобы они принимали объекты класса List, а монадные функции их возвращали.
Поправил. Теперь, вроде бы противоречий нет.
я как раз из тех, кто много слышал про монады, но так и ничего не понял. :) спасибо за ваши статьи, создаётся ощущение, что вот оно, прозрение, уже совсем близко. отметил, что теперь примеры, чисто внешне, стали напоминать код для jQuery, где функции должны всегда возвращать этот чудо-объект. :)
говорят, что достоинства или недостатки той или иной архитектуры определяются тем, насколько хорошо в ней локализуются изменения при изменении требований. как вы считаете, как много придётся менять в примере с птицами, чтобы сообщать причину и место падения пользователю явно (которые в примерах указаны комментариями)?
в классическом ООП я бы решал эту задачу при помощи специализации «управляющей коробки» (
говорят, что достоинства или недостатки той или иной архитектуры определяются тем, насколько хорошо в ней локализуются изменения при изменении требований. как вы считаете, как много придётся менять в примере с птицами, чтобы сообщать причину и место падения пользователю явно (которые в примерах указаны комментариями)?
в классическом ООП я бы решал эту задачу при помощи специализации «управляющей коробки» (
class TightropeWalkerProblem(Maybe): ...
). допустимо-ли подобное для монад? похоже, что изменение монады, каскадом, заставит изменять и все монадические функции, которые с ней связаны — возможно, лишь для того, чтобы они стали возвращать монадические значения нового типа. скорее всего, это не оптимальное решение, а правильное от меня ускользает. Можно сделать атрибут nothing монады Maybe не булевым, а строковым и поменять шорткат с nothing() на nothing(text), тогда любая монадная функция сможет возвращать текст ошибки.
кажется это нам не даст возможности указать на конкретную инструкцию, как в примере
show(
begin()
+'>>='+ to_left(2)
+'>>='+ to_right(5)
+'>>='+ to_left(-2) # канатоходец упадёт тут
)
Обычно, если и требуется только указать причину ошибки, то её одной и достаточно.
Но можно сделать и так:
Для ведения лога нужно начинать последовательность с justLoggable и все монадные функции должны быть декарированы посредством @loggable (или сами обрабатывать лог).
Но можно сделать и так:
#coding:utf-8
class Functor(object):
def fmap(self, func):
raise NotImplementedError()
class Applicative(Functor):
def applicate(self, monad_value):
raise NotImplementedError()
class Monad(Applicative):
def bind(self, monad_func):
raise NotImplementedError()
def then(self, monad_func):
raise NotImplementedError()
@property
def result(self):
raise NotImplementedError()
#------------------------------------------------------------------
class Maybe(Monad):
@classmethod
def just(cls, x):
return cls(just=x)
@classmethod
def nothing(cls, msg=''):
return cls(nothing=True, just=msg)
@classmethod
def _from_monad_value(cls, monad_value):
assert isinstance(monad_value, Maybe)
nothing, value = monad_value.result
if nothing:
return cls.nothing(value)
return cls.just(value)
def __init__(self, just=None, nothing=False):
self._just = just
self._nothing = nothing
def fmap(self, func):
if self._nothing:
return self.nothing(msg=self._just)
return self.just(func(self._just))
def applicate(self, monad_value):
if self._nothing:
return self.nothing(msg=self._just)
nothing, val = monad_value.result
if nothing:
return self.nothing(msg=val)
return self.just(self._just(val))
def bind(self, monad_func):
if self._nothing:
return self.nothing(msg=self._just)
return self._from_monad_value(
monad_func(self._just))
def then(self, monad_func):
return self._from_monad_value(monad_func())
@property
def result(self):
return (self._nothing, self._just)
just = lambda x: Maybe.just(x)
nothing = lambda msg='': Maybe.nothing(msg)
liftMaybe = lambda fn: lambda x: just(fn(x))
justLoggable = lambda x: Maybe.just((x, []))
def loggable(fn):
def inner(value):
value, log = value
log.append('%s\t%s' % (fn.__doc__, repr(value)))
new_nothing, value = fn(value).result
if new_nothing:
if value:
log.append(value)
return nothing('\n'.join(log))
return just((value, log))
return inner
#------------------------------------------------------------------
if __name__ == '__main__':
def _newPole( (l, r) ):
diff = l - r
if diff > 3:
return nothing(u'Слишком много птиц слева!')
elif diff < -3:
return nothing(u'Слишком много птиц справа!')
return just( (l, r) )
def seatLeft(x):
@loggable
def inner((l, r)):
u'''Налево'''
return _newPole( (l + x, r) )
return inner
def seatRight(x):
@loggable
def inner((l, r)):
u'''Направо'''
return _newPole( (l, r + x) )
return inner
banana = lambda x: nothing(u'Ой, корка!')
banana.__doc__ = u'''Корка под ноги'''
banana = loggable(banana)
nothing, val = reduce(
lambda m, v: m.bind(v), # свёртка посредством bind
[
seatLeft(2),
seatRight(4),
seatLeft(-1),
banana
],
justLoggable( (0, 0) ) # начальное значение
).result
if nothing:
print val
else:
print 'OK!'
Для ведения лога нужно начинать последовательность с justLoggable и все монадные функции должны быть декарированы посредством @loggable (или сами обрабатывать лог).
Написал новую версию. Описание изменений и ссылка в топике есть. Она непротиворечива (надеюсь), более соответствует принципам ООП.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Монады в Python поподробнее