Выполняю обещание, данное в предыдущей заметке, и пытаюсь объяснить, почему мне не понравилась статья Евгения Кирпичёва “Элементы функциональных языков”. Если коротко, то тремя вещами:
- не соответствием поставленной цели и содержимого статьи;
- стилем статьи и
- приведенными в статье примерами.
Прежде, чем переходить к более подробному изложению, хочу предупредить, что все нижеизложенное имеет ярко выраженный субъективный оттенок. Сегодня я буду в роли злобного читателя ;)
Поставленная цель и содержимое
Во введении указывается, какую цель преследует автор:
Данная статья ставит себе целью построить «мостик» между этими двумя разновидностями учебных материалов. В ней будут кратко описаны наиболее важные концепции из функционального программирования, с акцентом на перспективы их практического применения или имитации в нефункциональных языках. Таким образом, неподготовленный читатель сможет окинуть взором богатство имеющихся в функциональном программировании идей, понять, насколько они могут быть ему интересны и полезны, и, возможно, продолжить изучение интересной области. Подготовленный же читатель найдет в статьях на знакомые ему темы множество отсылок к литературе о более сложных проблемах, связанных с описываемыми концепциями — а темы некоторых статей, возможно, окажутся для него новыми.
Во-первых, я думаю, что автор ошибся с выбором цели. Он решил сделать статью, которая бы подошла по своему уровню как новичкам в ФП, так и более опытным функциональщикам. Это изначально очень сложная задача, поскольку новичков нужно плавно погружать в материал. Что должно сказываться как на языке изложения (максимально простом), так и на порядке подачи материала (постепенное усложнение), так и на объеме описательного и поясняющего материала. Но сделать все это нужно так, чтобы опытные читатели могли легко пропускать хорошо знакомые им части и сосредотачиваться на более сложных частях.
С этой трудной задачей автор не справился. Мне, как неопытному функциональному программисту (но уже наслышанному о многих из описанных вещей) было тяжело вникать в материал. Причиной тому и стиль письма автора (об этом ниже), и способ подачи информации, который оказался мне “не по зубам”. Думаю, что опытным функциональщикам читать статью гораздо интереснее, чем мне.
Во-вторых, прочитав введение я думал, что сейчас я буду читать материал, написанный в стиле “что следует знать об элементах функциональных языков”. Тогда как после прочтения у меня сложилось впечатление, что статья преследует цель рассказать “все, что я знаю об элементах функциональных языков”. По-моему мнению, автор не смог поставить себя на место читателя и критически оценить имеющийся у него багаж знаний (очень большой, нужно отметить). Как следствие – автор попытался впихнуть этот багаж в рамки статьи, но это оказалось неинтересно мне, как читателю.
В-третьих, ошибкой автора явилось отсутствие раздела о ленивых вычислениях. Поскольку по ходу описания автор часто ссылался на энергичность и ленивость. Но если читатель (не функциональщик) не имеет об этом никакого представления, то изрядная доля материала в статье либо ускользает от читателя, либо требует дополнительных усилий по поиску информации во внешних источниках.
Стиль статьи
Стиль статьи не понравился мне тремя вещами.
Во-первых, восторженными эпитетами. Такие речевые обороты, как:
В статье-«жемчужине» «Generic descrimination: sorting and partitioning unshared data in linear time» Фрица Хенгляйна…
Ralph Hinze, «Fun with Phantom Types» ([75]): превосходный обзор, описывающий: язык…
Ralf Hinze, Johan Jeuring, и Andres Löh, «Typed Contracts for Functional Programming» ([76]): интереснейшая статья…
…а также в потрясающей диссертации Нила Митчелла «Transformation and analysis of functional programs»…
лично мне в техническом тексте кажутся чужеродными. Сюда же можно отнести и другие характеристики в превосходной степени, которые автор дает статьям/книгам, примерам и внешним инструментам. Например, “в его знаменитой работе”, “в его знаменитой лекции”, “в его знаменитой статье”, “знаменитая библиотека” – все это вызывает смешанные чувства: сам себя ощущаешь неучем (поскольку ничего раньше об этих “знаменитых” вещах не слышал), но и подозреваешь, что эта знаменитость достигнута в очень узких кругах ;)
Во-вторых, подача материала с отсылками читателя к будущим разделам, для меня оказалась сложной для восприятия. Например, в разделе 5.7 упоминается какая-то система уравнений, которая будет обсуждаться только в разделе 5.10. А в разделе 5.9 дается рекомендация читателю поискать материал самостоятельно где-то в разделе 5.11:
Для всякого алгебраического типа определена чрезвычайно общая операция свертки (см. 11), абстрагирующая способ вычисления «по индукции» (снизу вверх) над значениями такого типа. Предлагается обратиться за разъяснениями к соответствующей подстатье.
При том, что раздел 5.11 так же не маленький и более точное указание места, на которое следует обратить внимание, мне бы лично не помешало.
В-третьих, и это гораздо важнее всех предыдущих пунктов, стиль письма автора слишком “наукообразен” и сложен. Для восприятия текста статьи нужно обладать гораздо большими изначальными познаниями, чем это было у меня:
В программировании данная идея впервые появилась, вероятно, в конкатенативных языках, таких как FORTH.
Вот вы раньше слышали о конкатенативных языках? Я нет.
Сюда же я бы отнес и использование автором некоторых понятий, которые либо не раскрываются в статье (например, “В языке Haskell соблюдается ссылочная прозрачность…” – хорошо, если читатель знает, что это такое), либо раскрываются уже после того, как были упомянуты (например, CPS-преобразования).
Ну а главное в стиле автора – это “заумность” текста. Это проявляется как в подаче материала (скажем, главу о свертках я так и не понял, еще хорошо, что о foldl и foldr я читал раньше и имел опыт их использования в Scala), так и в отдельных фразах.
Вообще, некоторые фразы в статье заслуживают того, чтобы быть процитированными. Хотя бы в качестве предостережения о том, как не нужно писать статьи для обычных программистов :
При дефункционализации программ высшего порядка зачастую получаются знакомые и несколько неожиданные алгоритмы: к примеру, из программы, составляющей список узлов дерева при помощи разностных списков, получается программа, использующая хвосторекурсивный алгоритм (см. 7) с аккумулятором.
Семантика бета-редукций (аналог «вызова функции») в лямбда-исчислении основана на подстановке, а не на, скажем, операциях над стеком параметров и адресов возврата, поэтому можно сказать, что вычислители, основанные на лямбда-исчислении, поддерживают оптимизацию хвостовых вызовов в изложенном далее по тексту смысле.
Monoid — класс типов, чьи значения образуют алгебраическую структуру «моноид» (см. статью «Моноиды в Haskell и их использование» (Дэн Пипони, перевод Кирилла Заборского) [137], а также презентацию Эдварда Кметта «Introduction to monoids» [98]), т. е. на значениях определена бинарная операция «комбинирования»:
mappend :: (Monoid m) => m -> m -> m
А также задан «нейтральный элемент» mempty :: (Monoid m) => m, являющийся единицей для mappend); а также похожий класс MonadPlus;
Ну и еще пара придирок по стилю:
- видимо, каждый раздел статьи раньше был самостоятельной статьей. Поскольку после их объединения в одну большую статью в тексте так и остались фразы вида “Часть способов имитации замыканий описана в статье о функциях высшего порядка (см. 3).” Это создает впечатление некоторой халтурности, т.е. отдельные статьи объединили в качестве разделов, но не изменили способ именования соседних частей большой статьи;
- лично меня напрягает, когда автор в тексте ссылается на свои собственные статьи в третьем лице: “см. статью «Изменяемое состояние: опасности и борьба с ним» Евгения Кирпичёва [180]”. Может это вполне допустимо и нормально, но меня раздражает.
Примеры кода в статье
К примерам кода в статье есть три претензии.
Во-первых, слишком большое количество языков было использовано для иллюстраций. Если не ошибаюсь, то в статье использованы: Haskell, Scheme, Python, C, Java, Matematica, Erlang, РЕФАЛ. Для меня это явный перебор. Для того, чтобы показать принципиальные элементы функциональных языков достаточно было бы использование всего лишь одного языка. Того же Haskell, к примеру.
Во-вторых, часть примеров (которые должны иллюстрировать удобство функционального стиля) в моем случае лишь вызывает нежелание программировать на функциональных языках. Поскольку, если такой код люди будут писать в промышленных проектах, то таким людям нужно сначала настучать по рукам, а затем объяснить, чем математика отличается от промышленного программирования. Продемонстрирую эту мысль на двух фрагментах:
- фрагмент кода для балансировки “черно-белых” деревьев (раздел 5.10):
balance :: Color -> Tree a b -> (a,b) -> Tree a b -> Tree a b
balance B (T R (T R a x b) y c) z d
= T R (T B a x b) y (T B c z d)
balance B (T R a x (T R b y c)) z d
= T R (T B a x b) y (T B c z d)
balance B a x (T R (T R b y c) z d)
= T R (T B a x b) y (T B c z d)
balance B a x (T R b y (T R c z d))
= T R (T B a x b) y (T B c z d)
balance color a x b = T color a x b
чтобы понять суть моих претензий, попробуйте представить, что в этом коде есть ошибка, и подумайте, как ее найти.
- наплевательское отношение функциональщиков к именованию сущностей демонстрируется в разделе 10:
toUnixMillis :: String -> Integer
toUnixMillis s = case (parseDate "YYYY/MM/DD hh:mi:ss" s) of
Date y mon d h m ss -> ss + 60*m + 3600*h + ....
Вот интересно, почему параметру ss (это секунды) не было дано имя s, по аналогии с параметрами d, h, m? Потому, что имя s уже занято? А до имени sec не удалось додуматься? Или же это экономия на спичках? За такой код своему подчиненному я бы устроил выволочку и заставил бы переписать, например, так:
toUnixMillis :: String -> Integer
toUnixMillis strDateTime = case (parseDate "YYYY/MM/DD hh:mi:ss" strDateTime) of
Date year mon day hour min sec -> sec + 60*min + 3600*hour + ....
В-третьих, я не понял, зачем был приведен ряд примеров. Скажем, примеры на языке пакета Mathematica в разделе 10. Может быть они интересны изучающему этот пакет, но не мне. Или, к примеру, фрагмент из раздела 9:
data JSValue = JSNull
| JSBool Bool
| JSRational Bool Rational
| JSString JSString
| JSArray [JSValue]
| JSObject (JSObject JSValue)
Вроде бы он должен был показывать нетривиальное использование алгебраических типов, но что в нем нетривиального я не понял.
Ну и другие непонравившиеся мне моменты
Здесь я приведу некоторые фрагменты статьи и реплики, которые у меня вырвались, когда я их читал.
Тем не менее, в течение очень долгого времени в силу ряда трудностей реализации (см. секции «Имитация» и «Реализация») «промышленные»-языки предоставляли крайне ограниченную поддержку функций высшего порядка.
Тут хочется напомнить про Smalltalk и его блоки кода, которые суть функции высшего порядка и замыкания. Никаких проблем с реализаций ни в конце 70-х, ни в 80-х с этим делом не наблюдалось.
Из-за некоторой путаницы с тем, что понимать под порядком функции при наличии каррирования (считать ли isAsubstringOfB функцией второго порядка из-за того, что результат ее применения к одному аргументу — функция первого порядка типа String -> Bool?), иногда порядком функции считают глубину самой вложенной слева стрелки: ниже приведены три типа, соответствующих функциям первого, второго и третьего порядков.
Ни из этого определения, ни из последующих примеров я так и не понял, что же такое порядок функции.
Таким образом, идея замыканий появилась еще до появления программирования, в момент создания лямбда-исчисления — в 1930 гг.
Существует мнение, что программирование появилось несколько раньше 1930 года, как попытка создания вычислительных программ для машины Бэббиджа. Не случайно первым программистом считают Аду Ловлес. Так что замыкания вряд ли появились еще до программирования :D
Статья появилась еще в то время, когда компиляторы были настолько неразвиты, что вызовы процедур вообще недолюбливали из-за низкой производительности. Прошло более 30 лет, однако до сих пор многие программисты и даже разработчики языков не знакомы с доводами, приведенными в этой статье.
Первая статья из этой серии появилась, насколько я понимаю, в 1975 году. Когда проблем с производительностью вызова функций уже не было.
вообще говоря, алгебраические типы намного лучше подходят для описания структур данных и позволяют намного естесственнее записывать алгоритмы их обработки (при помощи сопоставления с образцом (см. 10)), чем структуры (записи) или объекты
Такое мощное утверждение нуждается в доказательстве.
Если попытаться параллелизовать содержимое цикла, произойдет страшная путаница, как почти всегда происходит при попытке параллелизовать вычисления с изменяемыми структурами данных без строжайшего контроля и титанических усилий.
Старые страшилки для маленьких детишек.
Что еще?
Еще складывается впечатление, что статья написана ученым от программирования, а не промышленным программистом. Поскольку я не представляю себе, чтобы практикующий программист мог прочесть и переварить такое количество теоритического материала, сколько его упомянуто в статье. Это сильно снижает степень доверия к автору в том смысле, что я, как программист-практик, хочу знать о практической стороне повседневного использования функциональных языков. Тогда как автора, похоже, интересует научная сторона развития языков программирования.
Еще несколько умиляют и настораживают упоминания “интересных” задач и приемов программирования. Оно понятно, интересные задачи – это классно, это здорово. Редко, правда, с достойной оплатой совместимо ;)
Еще у меня было чувство, что автор стремиться продемонстрировать, что на функциональных языках все-таки программируют, что это не экзотика какая-то. Об этом говорят постоянные упоминания проектов из которых взяты примеры кода. Мол, это пример из такого проекта, а вот это – из такого. Мне, как читателю, это фиолетово (прошу прощения за свой французский). Пример кода должен иллюстрировать мысль автора. А взят ли он откуда-то или написал специально для статьи – не суть.
Итого
Статья большая, читать ее было трудно, полезной информации для себя я вынес мало. Часто выручал уже имеющийся у меня опыт, без него вообще бы мало чего понял.