amarao (amarao_san) wrote,
amarao
amarao_san

Category:

Функциональное программирование "от сохи".

Очередное поколение программистов пытается объяснить мне, что такое монады.

Надо сказать, что нынешняя попытка Дмитрия пока что самая успешная из всех; он услышал мои возмущения про определение сепулек через монадические функторы сепулек и начал объяснения с, собственно, функтора, и персонально монады Maybe (без фокуса на слове "монада").

Сейчас я попробую изложить объяснённное мне, старательно избегая любых отсылок к жутким теориям категорий и прочим внебрачным порождениям CS и математики.

В этой части: понятие функтора на примере монады Maybe.

Опишем проблему: часть функций может либо возвращать осмысленный результат, либо "ничего". Традиционно, в близких к Си языкам "ничего" кодируется либо как возвращаемое значение -1, либо как NULL, либо как None, либо как exception. В любом случае, его обработка довольно занудна:
t=time.gmtime()
if not t:
     print "Old version of python and midnight detected"


Главной проблемой подобного подхода является, фактически, удвоение кода. Нам надо в каждой строчке либо обрабатывать ошибку, либо, если отсутствие значения - это норма, то обрабатывать его отсутствие.

То есть
foo=bar()
if foo:
  baz = boo()
else:
  baz = None
if baz:
  q = fun1()
else:
  q = None


И т.д. В какой-то момент недрогнувшая рука программиста дрогается и мы получаем типовое AttributeError: 'NoneType' object has no attribute 'split'

Альтернативный подход состоит в том, чтобы каждая функция проверяла все передаваемые значения и правильно обрабатывала ситуацию. Но откуда простая библиотечная функция (например, file.write) знает, как правильно обрабатывать ситуацию?

Возникает желание сделать так, чтобы подобная нудная процедура происходила где-то внутри языка программирования/типов данных/библиотек и глаза человеку не мозолила.

Заметим, при этом нам хочется сохранить некоторый контроль, то есть жить в условиях, приближенных к JS никто не хочет ([,,,].join()",,"). Хочется иметь метод контролировать поведение кода, причём не средствами кода, а в описании данных. То есть, хочется иметь такой синтаксис, в котором
Maybe int и Must int будучи переданными в одну и ту же функцию в случае Maybe спокойно переживали отсутствие числа, а в случае Must - не переживали. (А в идеальном случае - вообще не давали бы скомпилировать программу).

Попробуем создать такой простенький фреймворк (Питон), который бы содержал в себе битик признака необязательности или обязательности.

Так как питон не поддерживает множественные конструкторы, мы схитрим, в одном конструкторе принимая 1 или 0 аргументов. Более того, если нам передадут не int, то мы его посчитаем как "нет значения". В реальности пишется не так, но для понимания идеи этого будет достаточно.

class Maybe():
    def __init__(self,*args):
        if args and isinstance(args[0],int):
            self.is_empty=False
            self.value=args[0]
        else:
            self.is_empty=True


В таком виде оно скучно.

Добавим во-первых repr, чтобы было что смотреть, а во-вторых метод fmap, который позволит нам применять некую функцию к значению внутри maybe (и то и то - внутри класса Maybe):


    def __repr__(self):
        if self.is_empty:
            return "Maybe (empty)"
        else:
            return "Maybe "+repr(self.value)

    def fmap(self, function):
        if self.is_empty:
            return Maybe()
        else:
            return Maybe(function(self.value))


Что мы делаем? Мы проверяем, "а есть ли значение?", и если значение есть, передаём значение в функцию, а его результат заворачиваем снова в "maybe". Другой Maybe, т.к. мы не хотим попортить наше значение.

Главное тут в том, что функция (pure_math, в примере ниже) передаваемая в fmap, не должна ничего знать про какие-либо maybe, и вообще, про тот ад и ужас, что у нас творится на входе. Она точно принимает на вход значение int. И мы это гарантируем в нашем функторе Maybe! Вот программа, которая это проверяет:

def pure_math(value):
    return value-1

tests=[None,1,2,3,"hello world", "5", "{'value':6}",dict,file('/etc/passwd', 'r'),9]

for item in tests:
    m=Maybe(item)
    print m, "-1 ==", m.fmap(pure_math)


Мы кормим несчастную функцию чем попало - None, строкой, числом в строковом представлении, json'ом, классом, открытым файлом - и невинная функция pure_math вместо того, чтобы яростно трейситься TypeError: unsupported operand type(s) for -: 'file' and 'int' нам покорно обсчитывает числа, и не пытается совершить суицид [оппозиция, Навальный, детская порнография, свобода слова, интернет-казино, экстремизм, взорвать, гексоген, права человека, бомба, революция, митинг, легалайз].

Ещё одно важное свойство - сама функция pure_math ничего не проверяет.

Вот вывод программы:
Maybe (empty) -1 == Maybe (empty)
Maybe 1 -1 == Maybe 0
Maybe 2 -1 == Maybe 1
Maybe 3 -1 == Maybe 2
Maybe (empty) -1 == Maybe (empty)
Maybe (empty) -1 == Maybe (empty)
Maybe (empty) -1 == Maybe (empty)
Maybe (empty) -1 == Maybe (empty)
Maybe (empty) -1 == Maybe (empty)
Maybe 9 -1 == Maybe 8


В этой конструкции нас смущает несколько вещей. Во-первых неприятный синтаксис. Мы должны вызывать fmap, в который передавать чистую функцию, вместо того, чтобы сделать pure_math(Maybe('fignya')). Во-вторых, проверка на тип входного значения происходит в рантайме, то есть мы готовы к отсутствию значения, но проверка на isinistance(args[0],int) - это чит, связанный с питоном. Хочется, чтобы проверка происходила в момент компиляции (запуска), и если мы туда подкладываем файл вместо инта, то предупреждение нам выдали бы на этапе запуска.

К сожалению, python такого не позволяет. Но речь-то не про питон, а про идею. Maybe, работающая в качестве "конейнера" - это и есть функтор.

(В следующей части, после того, как я уточню, всё ли я правильно понял, появятся сами монады, как обобщение любого рода контейнеров, а не только Maybe).
Tags: haskell, python, ФЯП, программирование
Subscribe

  • Greg Egan - Permutation City

    Я с удивлением узнал, что это 94ый год. Степень актуальности описанного не поменялась ни на йоту, не смотря на 20+ лет. Как многие книги Эгдана, в…

  • Greg Egan - Orthogonal

    Крайне хардкорная научная фантастика. Всё впечатление от книги (трёх книг!) можно разделить на несколько частей: * футуризм. У автора отсутствует…

  • The Long Way to a Small, Angry Planet

    Я редко дропаю научную фантастику, но такое случается. В основном, если оказывается, что вместо фантастики мне пытаются втюхать другой (не…

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 4 comments