All streams
Search
Write a publication
Pull to refresh
30
0
Алексей Пирогов @Astynax

Пользователь

Send message
Охранное выражение похоже на фильтр. Его можно отобразить в виде «коробки», которая будет иметь квадратное отверстие со стороны x<-xs, т.е. на входе, а сто стороны выхода будет иметь фигурное отверстие. Получится что-то, вроде «из всех xs пролезут только подходящие».
Поддержу предложение подумать о возможном смещении отображения от прямого повторения синтаксиса к отображению смысла.
Предложу вариант:
_X_Y_ [ тут охранное выражение ] X<-XS, Y<-YS
Посмотрел исходник cmp_to_key. Вот он:
def cmp_to_key(mycmp):
     """Convert a cmp= function into a key= function"""
     class K(object):
         __slots__ = ['obj']
         def __init__(self, obj):
             self.obj = obj
         def __lt__(self, other):
             return mycmp(self.obj, other.obj) < 0
         def __gt__(self, other):
             return mycmp(self.obj, other.obj) > 0
         def __eq__(self, other):
             return mycmp(self.obj, other.obj) == 0
         def __le__(self, other):
             return mycmp(self.obj, other.obj) <= 0
         def __ge__(self, other):
             return mycmp(self.obj, other.obj) >= 0
         def __ne__(self, other):
             return mycmp(self.obj, other.obj) != 0
         __hash__ = None
     return K

И что мы видим? Сравнение экземпляров разных классов (синглтонов, по сути), причем при каждом сравнении происходит вызов cmp. В итоге имеем те же затраты на cmp + overhead от ООП-обёртки, причем overhead немалый!
ИМХО, очень некрасиво реализовано.
По поводу functools.cmp_to_key — только в Pyhton версии 2.7+, при том, что 2.6 ещё в ходу.

cmp может быть ленивой. Пример:
cmp = lambda x, y: cmp(len(x), len(y)) or cmp(sum(x), sum(y))

Суммы считаться не будут пока длины не равны. В общем случае сумма будет считаться не для каждого элемента.
Теперь key:
key = lambda x: (len(x), sum(x))

Сумма будет посчитана для каждого элемента, даже если не будет использоваться.
Резюмирую: key не всегда лучше cmp
Обычно, если и требуется только указать причину ошибки, то её одной и достаточно.
Но можно сделать и так:
#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, а монадные функции их возвращали.
Как раз монады не есть что-то высокотехнологичное, это обычный инструмент, причем очень удобный. Просто так уж вышло, что полезны они больше в ФП :)
Это только одна монада из многих, а они очень разные.
Самые разные монады напрямую используются в функциональных языках. Некоторые реализованы и для императивных языков.

Если объект имеет метод, который возвращает сам объект, мы можем писать так:
some_obj.set_name("abc").inrease_age().update_items([1,2,3]).validate().save()

Это тоже частный случай монады, просто он более привычен людям, привыкшим к ООП.
По крайней мере поведение функтора можно реализовать и использовать — он вполне pythonic. Это просто метод контейнерного класса, позволяющий применить простую функцию к данным внутри контейнера. Например, аналог map, но для дерева.
А процедурное программирование — это просто программирование машины с Неймановской архитектурой. Т.е. если мы программируем последовательность действий, которые работают с памятью, наше программирование — процедурное.
Стек — память? -Да. Операции над стеком последовательны и меняют стек? -Да. Стековые языки — процедурные.
Ось ООП/не-ООП и ось ФП/императив — практически ортогональны.
В стековом языке отлично реализуется ООП, если он нужен для задачи.

if перед ветками или после — это конкретная форма записи выражений: префиксная, или постфиксная. И она никак не связана с парадигмой ООП/не-ООП.
Вот в функциональных языках if/then имеет другой смысл — условная конструкция не осуществляет управление потоком исполнения, а является вычислимым выражением (и поэтому всегда имеет ветку else)

В стековом языке постфиксную запись ветвления можно превратить в префиксную, только это не нужно — язык то весь постфиксный!
Поддержу высказавшихся выше: dataflow очень хорошо реализуется на стековых языках, и вполне читаем даже незнакомыми с концепцией людьми.

А ещё стековую программу можно очень просто останавливать, делать отпечатки состояния — ведь запомнить то нужно только текущую операцию и стек.
Скажем, интерактивный интерпретатор работает как виртуальная машина и сохраняет состояние между сессиями, помнит все новые слова и хранит введенные данные. Можно использовать как песочницу, как калькулятор (правда в ОПН) с памятью, диалоговую экспертную систему, и проч.

Также добавлю про 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)

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity