Pull to refresh
30
0
Алексей Пирогов @Astynax

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

Send message
— Python (отлично, в основном server-side: Django, Tornado)
— Clojure (изучаю, активно интересуюсь)
— Haskell (изучаю, активно интересуюсь)
— Scala (изучаю, активно интересуюсь)
— Erlang (небольшой опыт разработки)
— JS (начальный уровень)
— AVR C/С++ (хобби-проекты + опыт коммерческой разработки)

habrahabr.ru/users/astynax/
ru.linkedin.com/pub/alexey-pirogov/61/272/810/
Могу предложить вам посмотреть на интерфейс командной строки утилиты zenity — для простых случаев меню/диалогов в 1-2-3 уровня вложенности — самое оно! И можно прикрутить в текущую реализацию, например в качестве CLI при запуске библиотеки в роли скрипта.
В себе пока не чувствую сил теоркат объяснять, увы…

А for/yield это не просто map, это скорее filter/map/concat в одном флаконе:
class Book(title: String, authors: List[String])

// наименования книг, написанных Кнутом
for (b <- books; a <- b.authors if a startsWith "Knuth,") yield b.title

// авторы, написавшие более чем одну книгу
{ for {
    b1 <- books
    b2 <- books
    if b1 != b2
    a1 <- b1.authors
    a2 <- b2.authors
    if a1 == a2
  } yield a1
}.distinct
В замечательной книге «Learn You a Haskell for Great Good!» отлично всё написано про монады, моноиды, функторы. Причем описание идёт на практических примерах — без непосредственного введения термина, скажем «монада», но с постепенным «выкристализовыванием» чего-то простого, понятного, а главное — полезного. А потом это «что-то» и оказывается монадой ))

Я думаю, не стоит цитировать книгу на Хабре — она есть в свободном доступе (по крайней мере английская версия). Своими словами можно объяснить, но лучше чем в книге у меня лично вряд ли получится…

P.S. Option в Scala, это, всё таки, просто АТД, в который удобно завернуть результат вычисления, способного неудасться. И всё. Синтаксической поддержки нет (удобной, хотя бы как в Haskell), вычисления в контексте монадном почти никто не делает…
Если уж монады в Scala искать, так лучше обратить внимание на for/yield — вполне себе, монада списка сдобренная нужным кол-вом синтаксического сахара.

P.P.S. Объяснение «на пальцах» выше указанным терминам можно дать в практическом ключе, а фундамент находится в теории категорий, которую сходу на пальцах не объяснишь…
for x, y, z in product((1,2,3), (4,5,6), (7,8,9)):
    print x + y * z
len/min/max — это не функции даже, это обёртки над вызовом __len__/__lt__/__gt__ (ну может min/max посложнее чуток — там на входе итерируемый источник)
Т.е. len() возвращает длину объекта, который может её предоставить, min()/max() сравнивают сравнимые объекты, а их sort() сортирует, int()/str() приводит к целому/строке те объекты, которые сами могут представлять себя целым/строкой. Всё логично.

А range() именно такой, потому что имеет [start] и [step] — необязательные параметры, и такая сигнатура в общем случае не позволяет определить начальный элемент, и уж тем более шаг! Поэтому поддерживаемый типы аргументов ограничены числами, и начало/шаг имеют значения по-умолчанию. Ну и range() чаще всего используется в качестве источника индексов для прохода по индексируемому контейнеру, а тут как раз целые числа используются в качестве индекса, и начальный элемент контейнера обычно имеет индекс «0».

Если бы можно было ввести такие маг.методы:
__min__ — минимальный элемент данного типа
__max__ — максимальный элемент данного типа
__succ__(__pred__) — следующий(предыдущий) за текущим объектом элемент данного типа
можно было-бы реализовать range более всеядным — по-умолчанию отсчет шел бы от __min__, а инкремент происходил бы через __succ__. Всё это реализуемо, но встроенные типы данных — нерасширяемы, поэтому смысла особого нет. Проще под ситуацию написать свой my_range(), c датой и дельтами :)

P.S. Ну и в конце концов, в документации к range() указано, что он работает с числами — всё прозрачно.
В целом, «магические методы» — это в первую очередь Методы, и уж во вторую — «магические». И как любые методы — должны быть определены для объекта где-то в его иерархии наследования: «a + b» это, всего навсего, «a.__add__(b)», только с некоторой долей синтаксического сахара, отсюда и вся «волшебность». И для datetime определен метод __add__, который может принимать объект timedelta в качестве аргумента.

А range() — обычная функция, реализованная с поддержкой чисел и ничего кроме. При этом ф-ция имеет проверку на вызов для других типов данных — и возбуждает исключение, причем вполне конкретное, с объяснением причины!
А как связана range() с магическими методами?

Касательно магии сложения/вычитания: обычно операции сложения/вычитания работают над объектами одного типа (если для их класса реализовано соответствующее поведение), а datetime/timedelta, это скорее исключение: нельзя сложить две даты — это лишено смысла, но можно изменить дату на некоторое кол-во фиксированных единиц (дней/часов/минут...) и нельзя увеличить дату на n месяцев/лет — кол-во дней плавает.

range() работает только с числами и «магию» не использует (за пределами операций над числами).
При этом её аргумент «шаг» — необязателен. А как тогда как ф-ция должна определять, чем инкрементить дату?
Можно написать всеядную «магическую» версию range() (я напишу «ленивый» вариант):
def range_(from_, to_, step):
    while from_ < to_:
        yield from_
        from_ += step

Этот вариант работать будет для дат/времени (да и для списков, скажем), но потребует обязательного указания объекта-инкремента. Такое поведение неявно, ИМХО.
reversed и срезы не то чтобы пересекаются — они работают с разными «интерфейсами» объекта.

Срез работает через метод __getitem__() объекта, причем этот метод должен уметь принимать экземпляр slice в качестве параметра.
reversed() работает только с объектами, которые имеют и __getitem__() и __len__(), но зато reversed() работает поэлементно и делает это лениво (итератор). При этом reversed() вызывает __getitem__() с целочисленным аргументом.

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

Ну и reversed()/срезы, это, так сказать, функциональный подход — результат отвязан от источника.
А reverse(), это ООП подход — метод объекта, изменяющий его состояние.
Дикая смесь Awesome и Gnome Panel :)
Библиотечка очень понравилась. Предложу ещё добавить:
import itertools
import trafaret as t

class Tuple(t.Trafaret):

    def __init__(self, *args):
        self._trafarets = map(self._trafaret, args)
        self._traf_cnt = len(self._trafarets)

    def _check(self, value):
        try:
            value = tuple(value)
        except TypeError:
            self._failure('value must be convertable to tuple')

        if len(value) != self._traf_cnt:
            self._failure('value must contain exact %s items' % self._traf_cnt)

        result = []
        errors = {}
        for idx, item, traf in itertools.izip(itertools.count(), value, self._trafarets):
            try:
                result.append(traf.check(item))
            except t.DataError as err:
                errors[idx] = err

        if errors:
            self._failure(errors)
        return tuple(result)


if __name__ == '__main__':
    pair = t.Or(Tuple(int, str), Tuple(str, str)) >> (lambda x: '%s%s' % x)
    print pair.check((1, 'a'))
    print pair.check(('2', 'b'))

Y-комбинатор позволяет в анонимной функции использовать рекурсию с использованием самой себя, без необходимости назначения ей имени — функция остаётся анонимной.

Скажем, у нас есть неанонимная рекурсивная функция вычисления факториала:
def fact(n):
    return n * fact(n-1) if n > 0 else 1
print fact(10)

Это работает, т.к. у нашей функции есть имя fact и мы можем по нему функцию вызывать внутри неё самой.

Но если мы захотим написать анонимную функцию вычисления факториала, мы столкнемся с проблемой — как нам внутри функции вызвать её саму, не имея имени.
Можно попробовать сделать так:
fact = lambda self, n: n * self(self, n) if n > 0 else 1
print fact(fact, 10)

В данном случае имя функции не встречается внутри неё. Но при внешнем вызове функции приходится передавать в качестве одного из аргументов ссылку на неё саму. Это можно обернуть так:
wrap = lambda fn, *args: fn(fn, *args)
fact = lambda self, n: n * self(self, n) if n > 0 else 1
print wrap(fact, 10)

Теперь мы её ещё немного дообернем:
y = lambda fn: lambda *args: fn(fn, *args)
fact = y(lambda self, n: n * self(self, n) if n > 0 else 1)

Вот y и есть Y-комбинатор, к тому же анонимный. Пользуем inline как то так:
# применяем анонимную рекурсивную функцию
map(
    (lambda fn: lambda *args: fn(fn, *args))(
        lambda self, n: n * self(self, n-1) if n > 0 else 1),
    [1,2,3]
)

PROFIT! :)
Ну правильнее лучше чем короче )) Да и метакласс не сильно длиннее вашего класса будет. Зато метакласс можно подмешать (например через декоратор) к любому (ну почти) существующему классу, не навязывая наследование. Это несомненно плюс!
Тем не менее аргументы могут понадобиться, как параметры создания первого экземпляра, поэтому всё же лучше предоставить возможность, а не наложить ограничение (ИМХО).

А повторный вызов с другими аргументами можно перенаправить в явный метод типа _update(...). Скажем у нас есть сквозной singleton-журнал, и повторное «инстанцирование» со строкой-параметром дополняет уже существующий журнал.

Опять же через параметры конструктора можно реализовать не singleton, но cache, ключами которого будут хэши параметров
Между прочим, предыдущий вариант на метаклассе был лучше: в данной реализации __init__ будет вызываться при каждом инстанцировании класса, вне зависимости от того новый экземпляр возвращается или ранее созданный! Т.о. придётся инициализацию переносить в метод типа __init2__ и вызывать вручную в __new__ при создании нового объекта, а __init__ оставлять пустым. Ну и потомки этого класса не будут иметь возможности иметь аргументы инициализации.
Или так:
x, y = (
    lambda s: s.groups() if s else (None, None)
)(
    re.search(r'(\d+)x(\d+)', '30x20')
)
                xs           ys
                  |             |
                 V           V
_x_y_ <- [ x "==" y ]

(Тут у меня что-то типа стрелок сверху вниз, надеюсь, понятно будет)
Тоже думал над вертикальном расположении «исходников», но охрана может не только X охранять, но и Y вместе с ним. Правда, такую комплексную охрану можно уже после объединяющей скобки поставить.

Information

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