November 28, 2013

Функциональный стиль в питоне

Пост чисто философский

Периодическое чтение кусков кода, написанных при обострении хаскеля головного мозга, выработало у меня четкую ассоциацию: функциональный стиль - это нечитаемо. Точнее стиль с множеством map/filter/zip. Вот немного облагороженный пример такого кода (автор считает, что с кодом все ок):

Без подсветки синтаксиса
some_res = ", ".join(
                    map(str,
                        filter(None,
                            map(lambda x: getattr(obj.zip, x, None),
                                ['a', 'b', 'c', 'd']))))

Без переписывания в многострочный вариант ориентироваться в нем вообще сложно:

Без подсветки синтаксиса
attrs = ['a', 'b', 'c', 'd']
attr_vals = map(lambda x: getattr(obj.zip, x, None), attrs)
non_none_attrs = filter(None, attr_vals)
some_res = ", ".join(map(str, non_none_attrs))

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

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

Без подсветки синтаксиса
x = some_func1(a, b, some_func2(c, d), e)

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

Следующий случай - это функциональный стиль в скале. Его чтение у меня не вызывает того чувства трясины, какое вызывал аналогичный код в python/haskell. Тот же пример на 'скалапитоне' выглядел бы так:

Без подсветки синтаксиса
some_res = ['a', 'b', 'c', 'd'].map(getattr(obj.zip, _, None))\
                .filter(None).map(str).join(",")

Если отвлечься от более удобной формы записи лямбды, то он все равно читается гораздо проще. Мне кажется дело в том, что он читается линейно слева направо, а не "вообще изнутри наружу, но местами слева направо", как читается код в питоне.

Это относится скорее не к функциональному стилю, а к процедурный vs ООП, но именно функциональный стиль провоцирует избавление от переменных и написание множества вложенных вызовов функций. Он как лакмусовая бумажка вскрывает плохую масштабируемость читаемости процедурных выражений:

Без подсветки синтаксиса
a(x, b(c(), 1, 4), 'd')
# vs
c().b(1, 4).a(x, 'd')

К сожалению питон чаще всего не позволяет писать сцепленными методами, поскольку бОльщая часть методов возвращает None вместо self (а именно все, которые модифицируют объект на месте), а map/filter - функции, а не методы.

Итого я для себя сменил идею с "функциональный код нечитаем" на "функциональный код, написанный в процедурном стиле, нечитаем".

Ссылки:
          ananthakumaran.in/2010/03/29/scala-underscore-magic.html

Исходники этого и других постов со скриптами лежат тут - github.com/koder-ua. При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.

1 comment:

Vignesh said...
This comment has been removed by the author.