?

Log in

Originally published at Wonder what's next. You can comment here or there.

В одном пописываемом мной C++-проектике после перехода на C++11 начались проблемы на некоторых компиляторах, связанные с implicit move ctor, и поэтому я решил поковыряться в move semantics. В процессе нашел такой забавный баг особенность дизайна языка.

Рассмотрим относительно minimal reproducing example, демонстрирующий проблему:

  1. #include <string>
  2. #include <iostream>
  3.  
  4. struct A
  5. {
  6. std::string s;
  7.  
  8. A () : s ("Hi!") { }
  9. A (A&& a) : s (std::move (a.s)) { }
  10. A (const A& a) : s (a.s) { }
  11. };
  12.  
  13. struct B
  14. {
  15. const A& a;
  16.  
  17. B (const A& obj) : a (obj) { }
  18. ~B () { std::cout << a.s << std::endl; }
  19. };
  20.  
  21. A f1 ()
  22. {
  23. A a;
  24. B b (a);
  25.  
  26. // volatile bool t = false;
  27. // if (t)
  28. // return A ();
  29.  
  30. return a;
  31. }
  32.  
  33. int main ()
  34. {
  35. A a1 (f1 ());
  36. }

Для пущей связи с реальной жизнью можно считать, что A — некий ресурс, B — что-то вроде RAII, связанное с A.

Если строки в f1 () закомментированы (как в коде выше), то мы получим относительно предполагаемое поведение: программа при запуске выведет на экран Hi!. А если их раскомментировать, то начинается интересное — на экран будет выведена пустая строка.

Разгадка довольно проста, хоть и неочевидна сходу. Когда из функции может вернуться только один объект, пусть и с некоторыми преобразованиями, то можно применить RVO, сконструировав объект сразу в том месте, где он будет использован. То есть, компилятор видит, что из функции всегда возвращается вон тот объект a, и создает его прямо «в объекте» a1.

Если добавить еще один return с еще одним объектом, то компилятор больше не может применить RVO. Действительно, какой из двух объектов конструировать на месте a1? А так как для A определен move constructor, то компилятор и выполняет этот самый move, после чего объект a остается хоть и в валидном состоянии, но в непонятно каком. В данном случае вызывается move ctor для std::string, который убирает из исходного объекта строку, заменяя ее пустой — это эффективнее и быстрее тупой копии.

Лично мне кажется, что было бы разумно рассматривать move ctor как этакий полудеструктор, и не разбивать move-out и dtor. То есть, move ctor станет отложенным и выполнится не в момент return'а, а перед вызовом деструктора a. В однопоточной модели машины, которую вроде и использует стандарт C++, это едва ли бы привело к каким проблемам, а выглядело бы логичнее. В частности, в приведенном выше коде все работало бы по-старому — деструктор b видел бы еще старое состояние a, которое и должно быть.

Впрочем, люди с #c++ на фриноде говорят, что так о move ctor'ах думать нельзя. Я так и не понял, почему.

Короче, теперь C++ стал еще более бесконечно сложным. Теперь даже от return можно ожидать чего угодно. И теперь нужно быть аккуратнее, потому что добавление всяких разных строк-операторов-вызовов может сломать тонкие комбинации оптимизаций где угодно и привести ко всяким забавным эффектам.

ИМХО explicit return-move было бы круче. Что-то типа return std::move (a);, да.

P. S. Похоже, что всякие условия с волатильными переменными не нужны. По крайней мере, все из проверенных версий gcc не делают RVO даже в случае второго return сразу после первого, который уже явный unreachable code. Интересно, почему.

Pattern-matching FTW!

Originally published at Wonder what's next. You can comment here or there.

Все никак не привыкну мыслить целиком в функциональном стиле, с паттерн-матчингами и прочими ништяками.

В одном моем проектике (который эти самые пресловутые ГА) нужно было представлять математические выражения деревьями и всю работу с этими выражениями делать через эти деревья. Соответственно, был статичный словарь ([(a, b)]) идентификаторов функций и, собственно, самих функций. Вернее, два таких словаря, для унарных и бинарных функций. Что-то типа такого:

  1. data UnaryFunc = Sin | Cos | Log | Tan | Asin | Acos | Atan
  2. deriving (Show, Eq, Ord)
  3.  
  4. data BinaryFunc = Plus | Minus | Mul | Div | Pow
  5. deriving (Show, Eq, Ord)
  6.  
  7. unaryOps :: Floating a => [(UnaryFunc, a -> a)]
  8. unaryOps = [ (Sin, sin), (Cos, cos), (Log, log), (Tan, tan), (Asin, asin), (Acos, acos), (Atan, atan) ]
  9.  
  10. binaryOps :: Floating a => [(BinaryFunc, a -> a -> a)]
  11. binaryOps = [ (Plus, (+)), (Minus, (-)), (Mul, (*)), (Div, (/)), (Pow, (**)) ]

Соответственно, кусок функции вычисления значения дерева в точке, обрабатывающий вершины с функциями, выглядел как-то так:

  1. evalTree vars (NUn f t) | Just f' <- lookup f unaryOps = f' $ evalTree vars t
  2. | otherwise = error $ "Unknown uf " ++ show f
  3. evalTree vars (NBin f l r) | Just f' <- lookup f binaryOps = f' (evalTree vars l) (evalTree vars r)
  4. | otherwise = error $ "Unknown bf " ++ show f

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

COST CENTRE                    MODULE               %time %alloc
evalTree                       ExprTree              47.7   13.6                                                                                                                                                                                                                                                         
binaryOps                      Funcs                 17.2   47.9

Это собственные значения, без учета вызываемых в случае evalTree подфункций. И тут даже и близко нет ни операций над матрицами, ни вычислений якобиана, ни символьного дифференцирования. Ужас-ужас, короче.

Но тут стоит посмотреть, что список функций-то статичный, и, по факту, стоит вспомнить про паттерн-матчинг. Например, binaryFuncs перепишется следующим образом:

  1. binaryOps :: Floating a => BinaryFunc -> a -> a -> a
  2. binaryOps Plus = (+)
  3. binaryOps Minus = (-)
  4. binaryOps Mul = (*)
  5. binaryOps Div = (/)
  6. binaryOps Pow = (**)

Функция, вычисляющая значение дерева, в свою очередь будет выглядеть так:

  1. evalTree :: Floating a => [(String, a)] -> ExprTree a -> a
  2. evalTree _ (LC c) = c
  3. evalTree vars (LVar (Var v)) | Just c <- lookup v vars = c
  4. | otherwise = error $ "Unknown var " ++ v
  5. evalTree vars (NUn f t) = unaryOps f $ evalTree vars t
  6. evalTree vars (NBin f l r) = binaryOps f (evalTree vars l) (evalTree vars r)

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

В результате, правда, binaryOps все еще жрет 20% процессора и ответственна за треть аллокаций. Однако, стоит иметь ввиду такой очевидный факт, что компиляция с -prof -auto-all существенно меняет то, как исполняется код, и отменяет часть оптимизаций, поэтому ориентироваться на конкретные цифры бессмысленно. Главное — что скорость без профилирования существенно повыше стала.

В принципе, если бы не нужно было бы Ord и Eq от деревьев, то можно было бы хранить сами функции в вершинах вместо их идентификаторов.

С другой стороны, новые деревья у меня порождаются сравнительно редко, а вот значения вычисляются часто, в среднем тысячу раз на одно дерево, поэтому разумной оптимизацией может оказаться однократная подстановка функций в дерево. Этакий вариант типа-как-компиляции.

Правда, для этого придется еще пообмазываться type families всякими, ибо хранить уже скомпилированное дерево в том же ExprTree не получится, а генетические алгоритмы у меня работают не напрямую с деревьями, а с инстансами тайпкласса GAble a, поэтому нужно будет запилить что-то типа

  1. class (...) => GAble a where
  2. type Compiled a :: * -> *

Время

Мне больно и немного завидно смотреть на транжир. На тех, кто ежедневно смотрит телевизор. Кто спит в метро. Кто играет во всякие игрулечки на компьютере.

Времени слишком мало. Жизнь слишком интересная, и вокруг слишком дофига интересных вещей, чтобы вот так транжирить время.
У меня есть свой сраный опенсорс-проект. И еще идеи для минимум парочки.
У меня хаскель недоботанный. И еще уйма красивых языков, к которым я даже не приступал.
У меня интересная дипломная работа, и каждый день, когда я ей занимаюсь, у меня появляется еще ряд идей, каждая из которых вполне может превратиться в статью.
У меня просто работа тоже есть интересная. Даже полторы или две их, как считать.
Я хочу осилить нормально играть на гитаре.
Я хочу попробовать поиграть в группе. Попробовать себя минимум в парочке направлений. Но нет времени нормально репать, и, похоже, это никогда не сбудется.
Надо, блин, нормально заботать теорию музыки, в конце концов!
И это я не говорю о всей той математике и физике, которая вокруг, везде, такая вкусная и интересная.

Идеи роятся и рождаются в моей голове, а приходится их выкидывать. Некогда. Не сейчас. Не важно. Может не взлететь — недостаточно уверенности в успехе, выкидываем.

Я давно не читал книг нормально. Нет времени. Каждый раз, когда тянет почитать, как-то само себе напоминается, сколько еще несделанного.

Я ненавижу засыпать. Потому что сон — это еще 3-6 часов, вычеркнутых из жизни. А жизнь слишком коротка. Хотя это глупо и иррационально, да, если подумать — от недосыпа падает эффективность. Да и не тот я уже стал, что года три назад — гораздо тяжелее переносить 50-часовые марафоны.

Нафиг так жить, посоны?

Tags:

Originally published at Wonder what's next. You can comment here or there.

Сразу скажу, что в начале псотика будет много воды, самое интересное в последних N абзацах.

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

В общем, так как в хаскеле я все равно еще полный нуб, то решил и эту задачку решить на нем, потренироваться.

За 40 минут был написан текст программы:

Сорс также доступен по https://gist.github.com/1340356 , если у кого не вставилось.

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

  • uniDistr делает пару нормально распределенных чисел из пары равномерно распределенных, и возвращает остаток бесконечного списка этих равномерно распределенных. Для преобразования используется преобразование Бокса-Мюллера.
  • unis дергает uniDistr, чтобы преобразовать бесконечный список равномерно распределенных случайных чисел в случайно распределенные.
  • makeDoc по списку слов из словаря, требуемому размеру в символах и списку заранее сгенеренных случайных величин делает документ, используя makeDoc' как рекурсивно вызываемую «рабочую лошадку».
  • genSingle получает список слов из словаря, рандомный генератор и пару чисел — число документов и размер одного документа в символах. Дергает makeDoc, чтобы сгенерить каждый из документов. Результатом является список из пар (ИмяДокумента, СодержаниеДокумента).
  • gen делает примерно то же самое, что и genSingle, но только принимает не одну пару «размер-число документов», а их список, и для каждой такой пары дергает genSingle.
  • gs делает из одного случайного генератора бесконечный список случайных генераторов применяя unfoldr со сплитом.

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

По памяти могу сказать, что для массива слов в первом приближении использовался банальный список, и поэтому в строке 26 на операции индексации все адово тормозило — профайлер показал, что в этом месте жралось 97% процессорного времени, а скорость генерации была такой, что нужный мне объем текстов породился бы по моим прикидкам за пару суток. Впрочем, это довольно очевидное место — списки не очень круто использовать для случайного доступа, поэтому список был заменен на Data.Array, что существенно увеличило скорость генерации. Почти на три порядка, кажется, сейчас точно не вспомню.

Затем, рекурсивная makeDoc' сначала была написана не так оптимально. В первом приближении просто создавался список слов, запоминалось, какова их суммарная длина, и если длина превышала требуемую, делалось intercalate " " по этому списку слов. Тормозило, гадина, поэтому вызов был переписан на вот такой, хвосто-рекурсивный. Стало еще побыстрее.

И вот после этой пары оптимизаций (и еще пары совсем незначительных) я уперся в сам процесс генерации текстов. И тут мы подступаем к самому интересному: любой внимательный читатель заметит, что это просто отличный кандидат на распараллеливание! Пусть один тред генерит себе один текст, другой тред — другой, и так далее.

Лирическое отступление: ленивость хаскеля подразумевает, что в памяти хранится не сам результат вычислений, а некоторая отсылка к этому результату: запись о том, что должно быть вычислено и как. Только тогда, когда результат вычисления действительно понадобится, этот thunk будет вычислен. Такая запись называется thunk'ом, и подобная ленивость позволяет делать кучу интересных вещей, типа простого оперирования бесконечными списками. В программе, о которой этот псотик, бесконечные списки используются, например, для того, чтобы функция-генератор текстов вообще не парилась о природе случайных чисел и их генерации, а просто брала следующее число из списка. Для нее этот список бесконечный — из него можно будет достать любое количество элементов, а следующее случайное число вычислится как раз тогда, когда это будет нужно. На практике, конечно же, все всегда будет конечным — наши тексты конечны, в конце концов.

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

Для вычисления в нужном треде используются вещи из Control.Parallel.Strategies, в частности, parMap и parListChunk Последний позволяет задавать количество элементов, которые уйдут каждому треду, а это полезно, чтобы, например, наплодить не 1000 мелких заданий, а 8 крупных, так как у меня все же не 1000, а всего 8 ядер. Есть несколько разных стратегий вычисления, используемых с parMap/parListChunk. Нужная нам — rdeepseq. Эта стратегия позволяет проходить по всему списку thunk'ов, убеждаясь, что каждый из них вычислится там, где надо, а не в основном треде.

Окей, написали parListChunk, отлично, параллельно, но... Блин, жрет память как не в себя, гигабайтами на сколь значимо больших объемах данных. А почему? А потому, что теперь, как мы и просили, все санки действительно вычисляются сразу. До добавления этого parListChunk было просто: при записи на файл оказывается, что строку-то надо вычислить, а так как запись идет блоками, а не сразу вся, то и вычисляется, например, первые 16 килобайт. Или пусть даже 16 мегабайт, не суть, все равно мало. Вычислилось, записалось, больше не нужно, Garbage Collector за нами прибрал, память освободилась. Все отлично, много памяти в один момент времени не жрется, и ваще. А теперь каждый документ вычисляется сразу и весь держится в памяти до тех пор, пока не будет записан на диск, поэтому все логично и разумно.

Тем не менее, решение есть, причем достаточно тривиальное — использовать вместо parListChunk другой подход, с forkIO. В таком случае можно спокойно запустить несколько тредов, в каждом из которых вычислять по-старому, лениво и без насильного вычисления.

Gennl: part 0

Originally published at Wonder what's next. You can comment here or there.

Энное время я пописываю один матан-проект с реализацией на хацкеле, который по совместительству является моей бакланаврской дипломной работой на этом курсе. На днях читал на haskellwiki статью о том, как надо писать программы, и пришла мне в голову идея, что и правда было бы неплохо как-то освещать все это дело. Пожалуй, можно было бы писать как о хаскель-проблемах, возникающих при решении этой задачи, от «пишем собственные матрицы» до «блин-нахрена-экзистенциальные-и-дуальные-типы-при-дифференциировании-вперед», так и о сопутствующей математике. Кроме того, хотелось бы получить фидбек о проекте, начиная от «чувак, твой код — полное дерьмо» и до конструктивных обсуждений всякого матана.

Собственно, теперь о проекте. Проект заключается в применении эволюционных алгоритмов к задаче восстановления регрессии — иными словами, фигачим генетическими алгоритмами по множеству математических формул и смотрим, что лучше описывает какие-то там данные. Можно погуглить по «symbolic regression», например.

Помимо прикладной части (кода, реализующего все это дело), в работе есть какие-то попытки теоретически обосновать применимость этих алгоритмов. В частности, в тех работах (Джон Коза, Иван Зелинка), которые я видел (и про которые научрук говорит, что это более-менее bleeding edge), не обосновывается теоретически вообще ничего, а просто предлагается некоторый алгоритм, который типа как работает. Что, почему, зачем — непонятно. В то же время, я пытаюсь хоть как-то что-то обосновать-оценить, ну и, кроме того, есть ряд идей о том, как сам прикладной алгоритм можно еще улучшить. А еще было бы круто найти там где-нибудь групповую структуру или что-то такое, да.

Кроме того, значительная часть реализаций генетики на символьных выражениях работает с некоторым бинарным представлением этих выражений, откуда вылезают всякие интересные проблемы. То есть, выражение кодируется в бинарную строку, и ГА работает уже на этих строках. Таким образом мы можем получить рекурсивные функции (не очень приятно), для сохранения арности функций нужны отдельные костыли (не очень приятно), да и вообще. Я же работаю напрямую с соответствующими выражениям деревьями, и поэтому ряд проблем у меня тупо не возникает. Впрочем, алгоритм пока и не то чтобы работает :)

Делается все это на Haskell, публично доступно в соответствующей git-репе, и есть планы по дальнейшему развитию проекта вне этой самой бакалаврской работы.

Так что, в общем, если это кому интересно — попинайте меня. Комментить можно в стандалоне, в жоже, в пстоще, да и выцепить меня в конфочках math@conference.jabber.ru и c_plus_plus@conference.jabber.ru.

Why one shouldn't maintain their software

Originally published at Wonder what's next. You can comment here or there.

Ok, the title is a bit exaggerated. It's better to ask "why a free-time opensource developer shouldn't maintain his own software packages in different distros and OS", but it is too long for a title. And it still doesn't mention that I judge solely from my own very personal experience.

So, I'm a long time Gentoo Linux user. I have also been writing a piece of crap called LeechCraft for almost six years now. It is done in C++/Qt and it could be used on pretty any modern Linux distro. It is known to run and work on FreeBSD and Windows (and we have more or less regularly updated packages for the latter), and it even was built on a Mac a couple of times. Being a Gentoo Linux user, I maintain a set of ebuilds for LC (they even got into the main Portage tree about a month ago, but that's a different story), and I guess I shouldn't have even taken this responsibility.

Yes, maintaining packages is a responsibility. A user comes with a report regarding broken build from Gentoo ebuilds — then, as a maintainer, I should investigate it. I should ask the user about his system. Maybe it's some well-known third-party bug. Maybe the user is another ricer and has put every flag he could find in man gcc in his $CFLAGS. Or another hundred of reasons where the problem is in the user's system, not in my ebuilds or my sources. That job is normally taken by distro maintainers, not by core developers. And that takes time, that very precious time, and nerves. Personally, I'd rather introduce another nice small feature.

And even without users, sometimes the environment changes: not so long ago the git eclass had been superseded with git-2, and I had to investigate the difference, evaluate the pitfalls and the known problems and choose, whether/when it's time to migrate to the new eclass. And that also takes time and makes me do things I don't like, I'm not into, I have no expertise in. And yes, this job is also normally done by distro maintainers, not by core developers. Personally, I'd better fix another bug.

Ok, forget Gentoo: it's for freaks without girlfriends. Let's take something more popular: say, Ubuntu. First, I should write the proper scripts for building the package. And I have no experience with Ubuntu, I have no machines running Ubuntu or any Debian flavour at all, so how should I test it? Should I spend time I could be writing code (the thing I really like) or just sleeping for installing Ubuntu to a VM and learning its quirks?

Ok, dealt with scripts somehow. But then a new version of Ubuntu comes out, and it sometimes has renamed some packages, a couple of others have been split, and another couple has unsupported/untested versions. Or built in a strange configuration. Once again, I must spend time for finding all that out and dealing with it.

Ok, no new versions of Ubuntu, just another dependency for the application. It turns out the package with the corresponding library is named differently in different Ubuntu versions, and CMake version in a couple of Ubuntu versions has a bug that makes me write kludges for detection of that library. How should I test it, given that version of CMake is long gone in my good-ol' Gentoo? (Note: I'm so boring here cause this paragraph is based on a true story.)

And for each distro, I must find out what versions of what packages are available, what packages are absent at all, and package the latter ones. Twice as much pain. Or even more, depending on how much dependencies I must pull with LC (typically two at least).

And I won't even tell a word regarding Windows builds, since I prefer to avoid profanities.

What's killing me is the people that are positioning themselves as packagers, or meta-distro-leaders, or such, but who tend to put all that beforementioned pain back on me. Asking me to register on yet another openSUSE Build Service, ShmopenSUSE Build Service. Asking to build packages by myself. Why the hell should I take this job? People maintaining distros (or meta-distros, or anything in a ε-neighbourhood) are exactly those who should deal with all those issues, just by definition of the "distro". Offloading that job back to the developers is a great mistake from the duty separation point of view. What is remaining for you, guys, after all? You'd just put all those applications in one list and proudly call yourself as a new distro/metadistro/Qt-only-distro-initiative leader? Awesome.

I can't do things knowingly bad. I either take packaging and try to do my best or stay away from it completely. And if I register on that OBS and start building packages, then I automatically take the responsibility to deal with problems that end-user could (and will) encounter. And that's not my job, it doesn't give me the pleasure that writing code (and even hunting bugs!) gives. It's yet another pressure. Given only two choices — take the packaging or abandon the project, I'd chose the latter.

I don't like packaging, I don't like dealing with distro changes, I hate dealing with end user bug reports unless it's proven they relate to my application itself and can be fixed by modifying the source. Instead of doing all the things I hate, I'd better write some code or take my guitar and learn another Silent Civilian solo. Then why the hell should I also take maintainers' job that could be done by much larger amount of people than working with LC's sources and implementing the relevant ideas? Ah, forget LC sources, that applies to any non-trivial application.

And for those who like reminding about end users and that packaging would bring more of them — well, I don't do it for users. I don't care how much users actually use my software or do they use it at all. The only thing I care about besides the pleasure of programming — the opinion of other programmers whose opinion I respect, in turn.

And yes, I may be right as a programmer, but I'm surely wrong as project leader, but not that much. As a project leader, I should find the right people: after all, it's completely possible. And this post is for those who tend to think that it's me who must do the packaging.

And yes, this one is written in English, cause I hope it'd rise my self-esteem, of course.

И еще немного про лень

Originally published at Wonder what's next. You can comment here or there.

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

Люди верят, что они уследят за всем, за памятью, за порядком инициализации, за владением, за типом переданной переменной, whatever, и будут помнить через месяц или полгода, что у них было в голове, когда они писали данный кусок кода, из каких предположений они исходили и что хотели. Да что там, они (подсознательно) думают, что способны удержать в голове все детали, все мелочи, все крайние случаи их текущего алгоритма.

А я про себя знаю, что я человек, невнимательный, забывчивый, с неидеальным мозгом, вниманием, концентрацией, и так далее — короче, обычный такой человек. Я знаю, что даже сию минуту едва ли удержу все детали, а даже если повезет удержать — рассчитывать на это все равно нельзя. Поэтому если я хочу написать действительно работающий код, то постараюсь спихнуть на компилятор и куски рантайма (в том или ином виде) как можно больше задач. Пусть сами там следят за памятью, а я оберну в shared_ptr и убежусь здесь, что это корректно. Пусть сами там освободят ресурсы, а я просто передам в другой шаред_птр нужный делитер, или, так уж и быть, сделаю под это дело структурку.

Пусть, в конце концов, компилятор будет делать то, что у него получается на порядки лучше — будет вдумчиво и внимательно следовать моим командам, а мне останется писать на чуть более высоком уровне абстракций.

Ну а про тех, кто в ответ на все это говорит «а нефиг расслабляться», я даже не знаю, что и сказать.

Про лень

Originally published at Wonder what's next. You can comment here or there.

Одна недавняя дискуссия с одним упоротым фанатом продукции Microsoft намекнула на кое-какое распространенное заблуждение в умах современных айтишников. Да что там намекнула, ведь это и так давно понятно было, скорее, позволила выразить. Короче, значительная часть людей почему-то считает, что лень в программировании и сопутствующем — это что-то плохое. Что нельзя лениться, нельзя материться на инструменты потому, что они не позволяют быть ленивыми, и так далее. А ведь лениться полезно и хорошо.

Лениться нужно. Лень писать правила по удалению софтины из системы и правила обновления? Отлично! Изобретаем пакетные менеджеры, dpkg там всякие, ∃M∃RG∃, которые следят за тем, что и куда ставится, и сами за нами подотрут, а в ебилдах будет только минимально нужный набор правил. То, что под ШINDOWS это реализуемо через ту еще задницу — проблемы шиндошса, а не девелоперов под него, но это уже совсем другой вопрос.

Лень каждый раз руками дергать Init(T*)/Release(T*)? Отлично! Изобретаем конструкторы с деструкторами. Лень следить за памятью ну совсем руками? Изобретаем shared_ptr'ы и используем их. Лень писать num.Add(5)? Изобретаем переопределение операторов. Лень дублировать код? Изобретаем темплейты и прочие дженерики. Лень, в конце концов, писать везде std::map<std::string, std::vector<int> >::const_iterator и описывать типы функций? Выдумываем Хиндли-Милнера и пишем на удобных языках. Ну или, в худшем случае, придумываем auto или var и пишем на менее удобных языках.

Лениться — это хорошо и здорово. Лениться полезно и прекрасно, если это позволяет придумывать новые, более удобные и эффективные инструменты, позволяющие быть более продуктивным.

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

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

А пока пойду перечитывать zamotivator'а за то время, что не появлялся, да и некоторых других. Господа, кто меня еще читает, накидайте пзязя ссылочек на людей, годно пишущих о программировании, матане и прочих радостях жизни.

А в жуйке стало совсем уныло, например.

Учат в школе

Originally published at Wonder what's next. You can comment here or there.

Давно я не писал сюда. Это по большей части потому, что сейчас я обитаю в жуйке (да, сраный микроблоггинг поглотил и меня), учусь на втором курсе и въя^W работаю по трем направлениям.

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

Возьмем те же личкрафты. Казалось бы, отличный проект для командной работы: один программист пишет один модуль, второй — другой, третий — еще какой-то. Стандартизуешь API, если надо, между плагинами, и всё. А если плагины не связаны между собой (один пилит IM, а другой — плеер), то и этого не нужно. Но не все так просто.

Начнем с того, что ВНЕЗАПНО, несмотря на описания, хаутушки и мануалы (и даже гайд по написанию плагинов на примере простенькой скриншотилки), код среднему человеку, пытавшемуся тыкать личкрафты, кажется сложным. C++! STL! Qt! Boost!

IM, мессенджер, то бишь. Архитектура проста. Отдельным процессом ядро мессенджера, запускается, получает список .so-шек с реализациями протоколов от личкрафта, грузит их и использует для коннекта к серверу. Это чтобы от сервера не отключаться, когда личкрафты упадут. А ядро IM'а, в свою очередь, через какой-нибудь локальный IPC связывается с соответствующим плагином к личкрафтам, который уже рендерит ростер, показывает табы с чятиками и делает множество других интересных вещей.

API этого всего дела. Да не знаю я API. Не могу я сейчас предугадать, какие сегодня абстракции понадобятся для libpurple-based протокольного плагина, какие завтра — для iris-based, а через неделю — для SIP. API я буду рожать по мере написания кода. И изменять буду. И удалять старое, и добавлять новое. Да и код в моей голове. Архитектура в моей голове. Идеи и цели в моей голове. Я все это вижу, чувствую, но быстрее написать самому, нежели кому-то что-то объяснять.

Что делать, моя любимая жежешечка? И почему меня в моих физтехах учат какому-то жалкому подобию C++, учат умирающим технологиям типа SQL на примере MS SQL, но не учат таким банальным вещам?