Охранное выражение похоже на фильтр. Его можно отобразить в виде «коробки», которая будет иметь квадратное отверстие со стороны x<-xs, т.е. на входе, а сто стороны выхода будет иметь фигурное отверстие. Получится что-то, вроде «из всех xs пролезут только подходящие».
Поддержу предложение подумать о возможном смещении отображения от прямого повторения синтаксиса к отображению смысла.
Предложу вариант: _X_Y_ [ тут охранное выражение ] X<-XS, Y<-YS
И что мы видим? Сравнение экземпляров разных классов (синглтонов, по сути), причем при каждом сравнении происходит вызов cmp. В итоге имеем те же затраты на cmp + overhead от ООП-обёртки, причем overhead немалый!
ИМХО, очень некрасиво реализовано.
Обычно, если и требуется только указать причину ошибки, то её одной и достаточно.
Но можно сделать и так:
#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 (или сами обрабатывать лог).
Можно сделать атрибут nothing монады Maybe не булевым, а строковым и поменять шорткат с nothing() на nothing(text), тогда любая монадная функция сможет возвращать текст ошибки.
В целом, Вы правы — нужно бы возвращать объект класса List. Но данная реализация монад несколько своеобразна. Настоящее монадное значение — immutable. И после каждого >>= или >> мы получаем новое значение. Но конкретная реализация монад — это последовательность изменений первого контейнера в цепочке, что можно увидеть по коду методов applicate/bind/then. Это волевое решение: мне, в данном случае, показалась более удобной именно такая реализация — всё равно от нового монадного значения нужно только лишь содержимое. Начальное же значение упаковывается в экземпляр List только для реализации поведения, если бы можно было дополнить класс списка, я, безусловно, бы так и сделал. Таким образом монадные функции возвращают простой список (так их проще писать), но думать о нем нужно, как о монадном контексте.
Maybe-значение, по своей сути, тоже может быть представлено в виде простого кортежа (Bool, x). Можно было бы переписать just(), чтобы он возвращал просто кортеж. Тогда Maybe-последовательность нужно было бы начинать вручную с Maybe(just=x).
Кстати, в Haskell, в котором я и черпал вдохновение, список уже имеет монадное поведение, поэтому там монадные функции всегда возвращают список.
Спасибо большое Вам за то, что обратили внимание на несоответствие описания и кода. Можно переписать методы класса List, чтобы они принимали объекты класса List, а монадные функции их возвращали.
Это только одна монада из многих, а они очень разные.
Самые разные монады напрямую используются в функциональных языках. Некоторые реализованы и для императивных языков.
Если объект имеет метод, который возвращает сам объект, мы можем писать так:
По крайней мере поведение функтора можно реализовать и использовать — он вполне pythonic. Это просто метод контейнерного класса, позволяющий применить простую функцию к данным внутри контейнера. Например, аналог map, но для дерева.
А процедурное программирование — это просто программирование машины с Неймановской архитектурой. Т.е. если мы программируем последовательность действий, которые работают с памятью, наше программирование — процедурное.
Стек — память? -Да. Операции над стеком последовательны и меняют стек? -Да. Стековые языки — процедурные.
Ось ООП/не-ООП и ось ФП/императив — практически ортогональны.
В стековом языке отлично реализуется ООП, если он нужен для задачи.
if перед ветками или после — это конкретная форма записи выражений: префиксная, или постфиксная. И она никак не связана с парадигмой ООП/не-ООП.
Вот в функциональных языках if/then имеет другой смысл — условная конструкция не осуществляет управление потоком исполнения, а является вычислимым выражением (и поэтому всегда имеет ветку else)
В стековом языке постфиксную запись ветвления можно превратить в префиксную, только это не нужно — язык то весь постфиксный!
Поддержу высказавшихся выше: dataflow очень хорошо реализуется на стековых языках, и вполне читаем даже незнакомыми с концепцией людьми.
А ещё стековую программу можно очень просто останавливать, делать отпечатки состояния — ведь запомнить то нужно только текущую операцию и стек.
Скажем, интерактивный интерпретатор работает как виртуальная машина и сохраняет состояние между сессиями, помнит все новые слова и хранит введенные данные. Можно использовать как песочницу, как калькулятор (правда в ОПН) с памятью, диалоговую экспертную систему, и проч.
Также добавлю про DSL: вообще, в стековых языках программа расширяет язык под задачу (т.к. встроенный синтаксис обычно минималистичен), в итоге текст программы может стать вполне литературным ) И при этом в процессе дополнения языка обычно растет уровень абстракции и на определенном уровне в стеке уже могут лежать http-запросы, выборки из БД, целые файловые системы. И этими сущностями манипулируют слова не менее высокоуровневые:
Примерно так может выглядеть сервер хранения статики: взять_запрос запрос_картинки?
[dup вынуть_имя_файла достать_из_статики упаковать_картинку_в_ответ]
[drop "Отдаём только картинки" упаковать_строку_в_ответ] if
ответить
В этом случае нужно. Причем, случай крайний — для встроенных функций просто так оператор не перегрузишь, и декоратор не прикрутишь. Тут только так: Combinable(int) | str
или str | Combinable(int)
или хотя бы str | int | Combinable()
Каррированная функция, это всегда функция одного аргумента, причем всегда первого. Тут ноги растут из лямбда-исчисления, может я и не смогу правильно объяснить — моё понимание, скорее, интуитивно.
Частичное применение возможно везде, где есть замыкание и функции, как объекты первого порядка.
При частичном применении мы замыкаем часть параметров и возвращаем обычную функцию, просто с меньшим кол-вом параметров.
Каррированная функция каррирована насквозь:
f = lambda x: lambda y: lambda z: (x+y)*z
Такую функцию можно вызывать(для получения конечного результата) только так:
f(1)(2)(3)
На Python это некрасиво выглядит, а вот в Haskell всё отлично — там все функции каррированы всегда, и вызов выглядит проще:
f 1 2 3 == (((f 1) 2) 3)
Предложу вариант:
_X_Y_ [ тут охранное выражение ] X<-XS, Y<-YS
И что мы видим? Сравнение экземпляров разных классов (синглтонов, по сути), причем при каждом сравнении происходит вызов cmp. В итоге имеем те же затраты на cmp + overhead от ООП-обёртки, причем overhead немалый!
ИМХО, очень некрасиво реализовано.
cmp может быть ленивой. Пример:
Суммы считаться не будут пока длины не равны. В общем случае сумма будет считаться не для каждого элемента.
Теперь key:
Сумма будет посчитана для каждого элемента, даже если не будет использоваться.
Резюмирую: key не всегда лучше cmp
Но можно сделать и так:
Для ведения лога нужно начинать последовательность с justLoggable и все монадные функции должны быть декарированы посредством @loggable (или сами обрабатывать лог).
Maybe-значение, по своей сути, тоже может быть представлено в виде простого кортежа (Bool, x). Можно было бы переписать just(), чтобы он возвращал просто кортеж. Тогда Maybe-последовательность нужно было бы начинать вручную с Maybe(just=x).
Кстати, в Haskell, в котором я и черпал вдохновение, список уже имеет монадное поведение, поэтому там монадные функции всегда возвращают список.
Спасибо большое Вам за то, что обратили внимание на несоответствие описания и кода. Можно переписать методы класса List, чтобы они принимали объекты класса List, а монадные функции их возвращали.
Самые разные монады напрямую используются в функциональных языках. Некоторые реализованы и для императивных языков.
Если объект имеет метод, который возвращает сам объект, мы можем писать так:
Это тоже частный случай монады, просто он более привычен людям, привыкшим к ООП.
Стек — память? -Да. Операции над стеком последовательны и меняют стек? -Да. Стековые языки — процедурные.
В стековом языке отлично реализуется ООП, если он нужен для задачи.
if перед ветками или после — это конкретная форма записи выражений: префиксная, или постфиксная. И она никак не связана с парадигмой ООП/не-ООП.
Вот в функциональных языках if/then имеет другой смысл — условная конструкция не осуществляет управление потоком исполнения, а является вычислимым выражением (и поэтому всегда имеет ветку else)
В стековом языке постфиксную запись ветвления можно превратить в префиксную, только это не нужно — язык то весь постфиксный!
А ещё стековую программу можно очень просто останавливать, делать отпечатки состояния — ведь запомнить то нужно только текущую операцию и стек.
Скажем, интерактивный интерпретатор работает как виртуальная машина и сохраняет состояние между сессиями, помнит все новые слова и хранит введенные данные. Можно использовать как песочницу, как калькулятор (правда в ОПН) с памятью, диалоговую экспертную систему, и проч.
Также добавлю про DSL: вообще, в стековых языках программа расширяет язык под задачу (т.к. встроенный синтаксис обычно минималистичен), в итоге текст программы может стать вполне литературным ) И при этом в процессе дополнения языка обычно растет уровень абстракции и на определенном уровне в стеке уже могут лежать http-запросы, выборки из БД, целые файловые системы. И этими сущностями манипулируют слова не менее высокоуровневые:
Примерно так может выглядеть сервер хранения статики:
взять_запрос запрос_картинки?
[dup вынуть_имя_файла достать_из_статики упаковать_картинку_в_ответ]
[drop "Отдаём только картинки" упаковать_строку_в_ответ] if
ответить
Combinable(int) | str
или
str | Combinable(int)
или хотя бы
str | int | Combinable()
sequence = lambda *funcs: lambda x: reduce(lambda xx, f: f(xx), funcs, x)
map(sequence(int, str), [1.0, 2.5, 3.7])
Тут вместо композиции свертка с применением списка функций к значению. Результат тот же будет
Частичное применение возможно везде, где есть замыкание и функции, как объекты первого порядка.
При частичном применении мы замыкаем часть параметров и возвращаем обычную функцию, просто с меньшим кол-вом параметров.
Каррированная функция каррирована насквозь:
f = lambda x: lambda y: lambda z: (x+y)*z
Такую функцию можно вызывать(для получения конечного результата) только так:
f(1)(2)(3)
На Python это некрасиво выглядит, а вот в Haskell всё отлично — там все функции каррированы всегда, и вызов выглядит проще:
f 1 2 3 == (((f 1) 2) 3)