Зачем нужна Кложа
Когда спрашивают, зачем изучать Кложу, мы слышим что-то неубедительное. Репл, функциональный подход, неизменяемость. Это слабые аргументы. Их нет в мире тех, кто годами сидит в императивных языках. Слушатель не понимает, о чем речь, ему и так нормально. Еще прилепят Рича Хикки, и это уже не смешно. Его имя так затаскали, что один фанатизм.
Как же ответить, зачем нужна Кложа? Чем подкрепить точку зрения? Примерами из жизни.
Вчера я стал свидетелем, как типичный кложурист решает задачу машинально, без раздумий. А программист на императивном языке подвис. Вот как это было.
Сидели в офисе, работали, устали. Захотели почесать языком. Коллега пожаловался на функцию map в Питоне, и пошло-поехало. В итоге мы вышли на функциональное программирование и работу с данными в ФП-стиле. Заспорили, как лучше обходить коллекции.
К моему удивлению, нашлись те, кто вообще не понимал, что можно обрабатывать массивы данных функционально. Они и предложили задачу. Даны сведения о программистах, их возраст и рейт. Для каждого возраста нужно подсчитать средний рейт.
Я сначала не понял, почему это именно задача – в том смысле, почему над ней нужно думать. Совершенно рутинная вещь, два редьюса. Первый собирает все рейты по возрасту, второй считает среднее. Вот и все.
Можно свести два в один, как-то оптимизировать, но не суть. Главное, что кложурист вообще не видит здесь проблемы. Задача уже решена в уме, достаточно написать код в репле.
Три строчки: входные данные, промежуточный результат, итоговая свертка.
У коллег-императивщиков возникли трудности. Они стали именно думать над задачей, что-то чертить на доске, обсуждать. Для них это действительно задача, предмет мозговой деятельности. Мне вскоре понадобилось уйти, так что я не узнал, получилось ли решить или нет. Факт в том, что решения не было в первую же минуту.
Если бы я писал этот текст полгода назад, то поместил бы едкий абзац с травлей. Но сейчас я только сочувствую. Нормально решить эту проблему в Питоне нельзя. Мапы и редьюсы там кастрированные, в третьей версии редьюс вообще спрятали в недра библиотек. Во всяких Гоу ничуть не лучше.
Понимаете, нет инструмента! А нет инструмента, нет и понимания, что чего-то не можешь. Будешь сраться в чате, доказывая, что твой язык клевый. А как же ты узнаешь как эффективно работать с данными, если в языке ничего для этого нет? Замкнутая система.
По аналогии со словом: если нет слова, значит, нет целого домена в
сознании. Этой пустоты не видно изнутри, ее можно увидеть только со ступени
выше. Я искренне верю тем, кто пишет, что ему и так нормально. Верю,
нормально. Человек ко всему привыкает. Мы раньше и массив по i,j
обходили и
считали это нормальным. Но это не оправдание лени и нежеланию учиться.
Заглянем на StackOverflow. Каждый второй вопрос по Кложе связан с трансформацией данных. Типичный пост выглядит так: гайз, у меня вектор векторов мап, надо перевести в мап векторов мап. Как это сделать? И пример данных: что на входе, что на выходе. Автору кидают комбо из мап-редьюса, и вопрос закрыт.
Почему так много вопросов именно по работе с коллекциями? Неужели автор не умеет переводить список в словарь? Скорее всего, Кложа не первый его язык, и он уже программировал раньше.
Дело не в том, что в Кложе трудно обрабатывать коллекции. Наоборот, это сильнейшая ее сторона. Разница в том, что на этот раз автора заставили делать это правильно. Отсюда ломка.
Вспомним, как мы работаем с данными в императивном языке? Скажем, обходим список словарей. Начинается педерастия. Создаются списки и словари для накопления промежуточных данных. Код пробегает по коллекции три раза, хотя достаточно одного. Данные мутабельны, одни и те же ключи словаря перетираются.
В итоге, наплодив лишних переменных, обложившись костылями, императивный программист выходит на нужный результат. Решил задачу, но код лапша, все хрупкое. Внезапно, входная коллекция была изменена, пошли баги.
В Кложе нельзя так, просто нельзя. В ней есть изменяемые коллекции и стейт, но это высокая планка. Язык так устроен, что если ты взялся что-то менять, то подружись сперва с неизменяемостью.
Вы, наверное, скажете, что на вашей работе не нужно возиться с коллекциями. Но у меня коллекции постоянно. В кложе нет классов, объектов, наследований и всей мишуры. Все данные, неважно откуда их взяли – из файла, базы или сети – это комбо из векторов и словарей. Их нужно колбасить из одного вида в другой. Постоянно. Почти весь код – это операции с коллекциями.
Пришел от пользователя джейсончик? Нужно взять определенные ключи, состряпать мапу и положить в базу. Потом взять из базы другие данные. Плоский вектор мап перестроить в дерево и отдать пользователю. Это надо делать быстро, на автомате, без временных коллекций и счетчиков. Если подвисать на каждом обходе, то я вообще хз как вы работаете.
Возвращаясь к вопросу из заголовка – Кложа нужна затем, чтобы научиться работать с данными. Чтобы при одном только виде коллекции мозг автоматом выдавал комбо из map, reduce и filter.
Если вы пишете только на императивных языках, то скорей всего, не умеете работать с данными. Вам нормально, но вы не умеете. Не умеете, но вам нормально. Такой вот замкнутый круг. Это не страшно, и я до Кложи не умел. Посмотрел старый код на питоне и ужаснулся.
Нет трудности в том, чтобы сесть за Кложу на недельку и повтыкать, как работать с данными. Мир после этого станет другим.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter
Timur Malikin, 27th Jul 2018, link
Идея заметки понятна и в целом согласен, сам видел как "PHP-программисты" не знали как работать с коллекциями через "святую тройку" filter/map/reduce, хотя все эти функции в языке есть, и вполне пригодны. Есть также сторонние библиотеки которые делают это более человечно.
По поводу остального - почти все что написано можно применить к тому же Erlang или любым другим ФП языкам - работа с коллекциями, ломка мышления и тд. Но! Одна из киллер фич Clojure - это богатый мир Java библиотек, и ты не чувствуешь себя ущербным как в мире Erlang, где ситуация с библиотеками просто ужасна.
Поэтому, в случае Clojure, кроме мышления мы получаем полноценно развитый, с сильным сообществом "промышленный" язык, а не просто увлекательную игрушку для узкого круга задач.
Rrader, 27th Jul 2018, link
>В кложе нет классов, объектов, наследований и всей мишуры
А как же классы, объекты, наследования из Java?
Ivan Grishaev, 27th Jul 2018, link , parent
Оно есть. Но все это относится к термину inter-op, и с точки зрения данных неважно. Оно нужно лишь чтобы завести Кложурную машинерию, ну и порой слазить в кишки Джавы за чем-то нужным.
Илья Василевский, 27th Jul 2018, link
По тем же причинам можно взять Ruby и так же прекрасно жить :)
Rrader, 27th Jul 2018, link
А если я уже знаю filter/map/reduce и применяю в Java, то Clojure не нужен?
Никита Толкачев, 27th Jul 2018, link
Не понял, как из того факта, что мап, редьюс и фильтр удобны при работе с данными, следует, что нужна кложа.
И не понял, как тем ребятам, которые не смогли, помешал питон. Твои два редьюса заменить на два цикла, вот и все. Ты не думал над задачей не из-за кложи, а потому что это очень легкая задача. У тех ребят проблема по ту сторону экрана.
Dan Kozlov, 27th Jul 2018, link
Я вообще продакт-менеджер, который кодит на питоне на выходных из интереса, почему я решил задачу за 30 секунд, а императивные программисты не осилили?
https://pastebin.com/Vd6kxTC6
Abbath, 27th Jul 2018, link
На все готовы лишь бы recursion schemes не использовать :)
Vladimir Gordeev, 27th Jul 2018, link
Всё-таки не раскрыта тема почему именно Кложа. Из статьи понятно что и C# с его LINQ вполне замена.
Михаил, 27th Jul 2018, link
Спасибо за дельный совет! Обязательно начну учить кложу, потому что однажды кто-то смог на ней решить ОДНУ задачу быстрее, чем кто-то другой
Mikhail Khorpyakov, 27th Jul 2018, link , parent
Во-первых, условия задачи были другие.
Во-вторых, подвисли мы на моменте подсчёта ассимптотической сложности вычислений.
В-третьих, вот тайминги по трём решениям включая ваше (с доработкой в соответствии с оригинальными условиями задачи) на одном миллионе элементов:
https://uploads.disquscdn.c...
Если бы функциональный подход давал такую же производительность и вычислительную эффективность как императивный, мы бы давно увидели закат нефункциональных языков.
Lens, 27th Jul 2018, link , parent
я писал на PHP, помню как на заре, я в одном проекте написал на PHP несколько кейсов с использованием функционального подхода, везде вместо циклов и прочего использовал array_map/filter/reduce, на что мне мой тогдашний тимлид заявил чтобы я так не писал больше.
Тогда я не понял, сейчас я это понимаю, не все могут сходу читать такой код и в голове иметь представление о результате выполнения. Ну и как следствие, там где язык позволяет писать ООП или императивно, вам скорее всего попросту не дадут использовать функциональный подход по целому ряду причин. Например - мешанина будет, если один пишет в ФП стиле, остальные в ООП и циклах. Поэтому зачастую эти возможности в PHP попросту не используются. Хотелось бы думать что именно по этой причине он мне не дал оставить тот код.
Преимущество кложи в том, что код на кложе, и есть данные, а данные и есть код.
Гомоиконность. Но вот как это объяснить другим? :) Поэтому кложа удобна для трансформации данных, чем тот же Эрланг.
Мне кажется к этому можно прийти только самому. Я, работая как раз с PHP, очень быстро начал уставать от этого всего, и пришел к кложе. Оторвать меня было уже невозможно. Сейчас пишу на кложе и вспоминаю PHP и ООП как страшный сон. Надеюсь он мне больше не приснится.
Раньше у меня были попытки объяснять друзьям и коллегам, что это, зачем это, но я бросил попытки. Задал себе вопрос "Зачем?" и понял, что, не то что мне - им это не нужно. Тем людям которым нужно, сам придут, спросят.
Dan Kozlov, 27th Jul 2018, link , parent
> условия задачи были другие
Условие: "Для каждого возраста нужно подсчитать средний рейт"
У меня возвращается кортеж возраста и среднего по этому возрасту рейта. Где я неправ?
> вот тайминги по трём решениям включая ваше
Мне кажется, только глупый будет спорить с тем, что питон — это ебучий тормоз в мире программирования ¯\_(ツ)_/¯
Если бы у меня был миллион айтемов, я бы использовал pandas и numpy. Писать пример лень, но он не проиграет по таймингам кложуре, обещаю (только инициироваться будет полчаса). В последний раз, когда я играл с pandas и Jupyter Notebook, он у меня ворочал 20М строк в таблице без заметного лага.
Но в реальной жизни у меня не бывает миллиона айтемов в памяти. Может, у меня просто примитивные задачи, но всегда, когда у меня возникают миллионы айтемов, они… как правило в базе. А там SQL сделает за меня свои мапы и редюсы лучше, чем любая кложура, питон, руби и все остальные вместе взятые.
Кстати, я не ставил себе задачей написать оптимально. Хотелось написать читаемый однострочник, в противовес тому ужасу из скриншота у автора, я вот вообще нифига не понимаю, лол.
Это ж питон, тут всем насрать на производительность.
Mikhail Khorpyakov, 27th Jul 2018, link , parent
Требовалось найти максимальный из всех средних рейтов по возрастам. Замеры в одном файле на Питоне. Вот полный код файла который мы использовали для замеров: https://gist.github.com/kho.... Справедливости стоит заметить, что в первом варианте используется массив а не словарь, что и даёт некий прирост к производительности.
Dan Kozlov, 27th Jul 2018, link , parent
Теперь понятнее, что за три решения. Я сперва подумал, что вы перфоманс питона сравнили с кложурой, даже немного возмутился про себя.
Ваши решения автор осудит за императивную болтливость и промежуточные списки. Моё с этой точки зрения чистое, но очень медленное. Не надо давать себя втягивать в такие споры, в них победителем не выйдешь никогда.
Kir, 27th Jul 2018, link
Haskell is fine, too
Alexander Popov, 27th Jul 2018, link , parent
Люблю Ruby за возможность откинуть "промежуточные данные"!
https://repl.it/repls/Finan...
Flamefork, 28th Jul 2018, link , parent
...для некоторых значений слова "быстрее"
Konstantin Alekseev, 28th Jul 2018, link
идиоматик питон https://twitter.com/kvaleks...
Mike Ananev, 28th Jul 2018, link
https://uploads.disquscdn.c...
решение на Clojure через group-by в одну строку.
Namynnuz, 28th Jul 2018, link
Исходя из «описания», абсолютно не ясно, что именно требуется. Средний рейт чего? Всех программистов данного возраста? А результат должен быть словарём? Или достаточно списка туплов? Вот, значит, Go и Python плохие, кастрированные. И... Всё? Больше языков нет? В том числе и императивных? Я так и не понял, почему нужна Clojure, если абсолютно то же самое решается в C# таким же коротким LINQ-запросом. Только синтаксис не заставляет твои глаза кровоточить (представил в виде именованных туплов, можно было словарём спокойно, и работает оно с такими же списками, IEnumerable<t>; и не надо мне рассказывать про правильное сравнение double, да): https://i.imgur.com/ELLn3sk...
cactus, 30th Jul 2018, link , parent
Зато здесь заставляет кровоточить глаза и руки количество строк (и символов в каждой).
В итоге мы видим в вашем комменте, с одной стороны, опровержение посыла статьи (потому что сам запрос, вроде как, норм), а с другой — подтверждение превосходства окололиспов в целом и кложи в частности с точки зрения количества сущностей, которые необходимо держать в голове в каждый момент времени. Здесь явно видно, что в сишарпе их слишком много даже в такой простейшей операции.
Namynnuz, 30th Jul 2018, link , parent
О да, ведь три строчки это таааак много, просто голова отваливается... -_-
Давай хотя бы сравним количество символов и различных маловразумительных синтаксических элементов. Может ещё F# обсудим? Там это будет ещё короче и чище. При том, что он функциональный, но тоже нифига не LISP. И все эти «страшные длинные сущности» вводятся двумя-тремя символами + TAB.
https://i.imgur.com/TOzJ9oC...
Denis Kovalev, 7th Aug 2018, link , parent
@dkzlv:disqus Иван сильно утрирует в постановке задачи: нужно было найти максимальный средний рейт полученный для каждого из возрастов и сделать это на питоне в одну строку а-ля "100% функционально", используя только map/reduce/max/sum, не используя foreach, groupby, Counter и прочий сахар работы с коллекциями.
Василий Колесников, 11th Aug 2018, link
Очень!
Как будто читаю свои собственные мысли.
Василий Колесников, 11th Aug 2018, link , parent
Чем обусловлен instance_exec?
Alexander Popov, 12th Aug 2018, link , parent
Отсутствие переменных для `sum` и `size`.
Serhii Khalymon, 26th Jul 2019, link
У меня не компилится код со статьи выше:
(def data [{:age 18 :rate 30} {:age 12 :rate 22} {:age 33 :rate 22}])
(def _temp (reduce (fn [res item] (update res (:age item) conj (:rate item))) {} data))
(reduce-kv (fn [acc k v] (assoc acc k (average v))) {} _temp)
Syntax error compiling at (REPL:1:39).
Unable to resolve symbol: average in this context
Ivan Grishaev, 29th Jul 2019, link , parent
Потому что нужно определить функцию average, в коробке ее нет
Dmitry Ponyatov, 6th Aug 2020, link , parent
Очень просто -- вы просто принципиально не понимаете как писать неограниченно быстрые программы на функциональных языках 8) впрочем, функцианальщики тоже ведут себя как страусы -- не хотят высунуть голову из своего REPLа, и заглянуть хотя бы на хх.рю
Ключ -- не нужно решать _задачу_ на ФЯ. На нём нужно писать модель задачи, и компилятор модели в низкоуровневый язык (С/С++, LLVM, Java, что-угодно-в-проде).
Clojure (Racket, Haskell,..) принципиально не нужно тащить в прод, и не нужно показывать вашему работодателю или заказчику что вы им вообще пользуетесь -- им будет достаточна ваша эффективность по генерации кода на mainstream языке, используемом в команде. Вся мощь ФП может раскрыться в коммерческо-прикладном смысле на этапе проектирования, только в форме метапрограммирования, когда вы сами автоматизируете создание своих 50-70-95% рутинного кода.
Michael T, 3rd Mar 2021, link , parent
Нет, groupby работает только на отсортированных данных.
Да и mean лишнее делает, из-за него по сути тормозит.
Мопс, 13th Aug 2023, link
Отличный пример! Только тег “clojure” нужен, а не “clj”. Иначе до поста не добраться.
Ivan Grishaev, 13th Aug 2023, link
Поправил.