?

Log in

No account? Create an account
для программистов - Поклонник деепричастий [entries|archive|friends|userinfo]
Anatoly Vorobey

[ website | Website ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Links
[Links:| English-language weblog ]

для программистов [фев. 28, 2006|06:01 pm]
Anatoly Vorobey
Следующее будет интересно только программистам.

(вопрос: стоит ли мне создать отдельный журнал для записей на компьютерные/программистские темы? Я несколько раз об этом думал, но так ничего и не решил)

Один красивый хак, и две сырые мысли.

1. Tail-recursion в Питоне. Питон (Python) не поддерживает оптимизацию хвостовых вызовов. Если вы не знаете, что это такое, то вам следует об этом почитать, профессиональный программист должен об этом знать. Вот здесь понятным языком объясняется по-английски. В двух словах: если функция f заканчивает свою работу возвращением результата другой функции: return g(...) (неважен синтаксис, важна суть), то можно оптимизировать такой вызов, освободив место для аргументов и переменных функции f на стеке ещё до вызова функции g. Если g==f, т.е. f рекурсивная функция, то это позволяет делать сколь угодно глубокую рекурсию без лишней траты места на стеке. Это особенно важно в функциональных языках, но и в других удобно. Но не везде это можно сделать, зависит от языка.

В Питоне - теоретически можно, язык позволяет, а практически автор отказывается такими глупостями (с его точки зрения) заниматься. Так вот красивый трюк, который позволяет любую функцию в Питоне оптимизировать под хвостовую рекурсию, используя только возможности самого языка: "декораторы", позволяющие модифицировать поведение функций, и немного рефлексии. По-моему, этот код может понять и кто-то, кто не знает Питон (я, например, не знаю и понял). Суть его такая: из функции g, вызывающей самое себя, мы делаем func, которая при запуске проверяет свой стек, и смотрит, не является ли она своим собственным дедушкой по линии вызовов; если да, она кидает исключение, если нет, то вызывает g в блоке, ловящем такое исключение. Когда в выполнении g ("сына" func) дойдет дело до рекурсивного вызова g, вместо него подставляется вызов func опять (так работают "декораторы"), и она, обнаружив, что является своим дедушкой, кинет исключение; сама же в ипостаси дедушки его поймает и раскрутит стек на два вызова обратно; потом опять вызовет g, и так далее. В результате стек двигается на два фрейма вперед, а потом по исключению обратно на два фрейма назад, а рекурсия продолжается. Красиво!

2. Следующие рассуждения в принципе мне кажутся очень простыми, но ни разу их не встречал.

Одна из главных причин, почему C++ плохой язык: для этого надо сначала понять, почему C хороший. В чем состоит то свойство C, из-за которого его называют "портабильным ассемблером"? Дело не в том, что "близко к машине", и всё низкого уровня. Дело в том, что почти всегда в C эффект любой строки кода локален и очевиден. Когда я что-то делаю в C, неважно что, я очень хорошо понимаю, что именно происходит. Если я пишу x=y, я знаю точно, что происходит. Если я пишу f(...), я знаю точно, какая конкретно функция будет вызвана, я могу указать на неё пальцем, и я знаю точно, что произойдёт в момент входа в неё и выхода из неё. Если я выделяю память, я знаю точно, что она не исчезнет, пока я её не освобожу. Итд. итп. Атомарные строки кода переходят в атомарные куски кода во время запуска, и никаких сюрпризов. Есть исключения: например, если я вызываю функцию через ссылку, я не знаю, что собственно я вызвал, до рантайма. Но этих исключений очень мало и они тоже "локализованы" и их легко понять.

Это необязательно хорошо. Но это - в C - выполняется последовательно, и то, что это последовательно - хорошо. Разные языки по-разному решают вопрос о том, как позволить программистам прятать информацию от самих себя. В объектно-ориентированных языках принцип полиморфизма, принципиального незнания мной того, объект какого класса я вызываю по ссылке (базового или наследника), является краеугольным; и это по-своему хорошо, если проведено последовательно.

C++ - смесь разных принципов отношения к информации и средствам её прятать или открывать, которые доступны программисту; смесь, кажется, очень плохо продуманная. С одной стороны, полностью сохранён "низкий уровень" C, в том числе отсутствие сборки мусора, т.е. очень важный пример того, что заставляем программиста за всем следить и обо всём помнить. Множественное наследование - другой пример: если практически оказывается возможным его воплотить, мы его воплощаем, пусть оно концептуально сложно, пусть оно заставляет программиста выслеживать порядок вызова конструкторов, всякие ужасные "ромбики" и прочую хренотень.

Но, с другой стороны: полностью нарушен (я бы сказал, низвергнут с пьедестала и подвержен особо извращенному поруганию) этот самый принцип локальности поведения системы в ответ на строчку моего кода. Я всего лишь объявил переменную какого-то типа, написав "Typename varname;", но эта строчка может привести к вызову неизвестного мне конструктора, а за ним - кода сколь угодно, вообще говоря, сложности. Я всего лишь применяю известный мне оператор к переменной - а он, оказывает, overloaded у этого класса, и черт знает что на самом деле там произойдет. Я всего лишь вышел из функции, что может быть проще, написал }, а в рантайме на самом деле пошли плясать деструкторы всех автоматических объектов в этой функции. И даже и не буду начинать говорить про copy constructor и прочие подобные прелести.

Так вот, поэтому C++ - плохой язык.

Он настолько много прячет за кулисами, чтобы навязать программисту режим работы "моя хата с краю": пиши свой код, не волнуйся насчёт того, что магически происходит вокруг него, всё хорошо, всё идёт по плану... И в то же время того же программиста заставляет следить за всеми malloc()'ами и new, рассчитывать ужасные иерархии наследования и дикие функции-"френды", не говоря уж о темплейтах. По сути дела, медленно и неумолимо превращает программиста в шизофреника.



3. Монады и Хаскель. Это ещё более сырая и невнятная мысль, но попробую всё же высказать.

(если вы не знаете, что такое Хаскель и монады, примите условно такие определения: Хаскель - язык, в который нелегко "въехать", но очень мощный и интересный; монады - способ программирования внутри Хаскеля, в который очень, очень нелегко въехать, но тем не менее он фундаментальный и без него реальные большие и полезные программы на Хаскеле не сделать. Если вам интересно почитать больше о Хаскеле, см. сообщество ru_lambda и, например, мою незаконченную серию записей в нем).

С одной стороны, мне сейчас полагается быть фанатом Хаскеля вообще и монадического программирования в частности. Т.к. я только что это изучил (собственно, продолжаю изучать), и оно действительно мощно, интересно, необычно, полезно. И с одной стороны, я действительно теперь фанат и мне всё очень нравится (не отменяя другие любимые языки). С другой, есть интересные сомнения. prosto_tak задал правильный вопрос, на который у меня нет хорошего ответа: если оно всё такое сложное, зачем оно надо?

Мысль, которую я хочу передать, она примерно вот какая. Казалось бы, этот вопрос неправилен вот по какой причине. Каждый раз, когда мы учим язык (технологию, идею, итп.), который очень отличается от уже знакомых нам, например, впервые учим объектно-ориентированный язык, или впервые функциональный, как Хаскель, или первый раз сталкиваемся с Лиспом, или с APL, итд. итп. - неизбежно некоторые вещи будет тяжело понять, потому что нужно "перестроить нейроны" на другой тип мышления. Именно такие языки и интересны, которые действительно заставляют программиста мыслить совсем по-другому, а следовательно другим образом смотреть на мир. И стоит ожидать, что какие-то вещи в языке будет трудно понять, пока не перестроишь нейроны. И поэтому нечего жаловаться на то, что трудно, скажем, понять и пользоваться тем, насколько в SmallTalk глубоко и последовательно лежит идея "всё-объект", или в Лиспе код==данные итд. - или в Хаскеле монады!

Но после того, как я более или менее разобрался всё же с монадами (ну по крайней мере мне кажется, что разобрался), я понял, что вышеописанная точка зрения, которой я придерживался до того, неверна. Монады объективно тяжело понять. Их тяжело понять не только потому, что это "новый" тип мышления - функциональное программирование - но и вообще, просто, с объективной интеллектуальной точки зрения их тяжело понять. Они требуют намного больше абстрактного и типично-математического мышления, чем сложные возможности в любых других известных мне языках. А меж тем в Хаскеле даже строку на вывод не послать без монад (ну, можно, конечно, это делать, не понимая, как это работает, но это ж неинтересно).

Я не то чтобы жалуюсь, нет - я же понял в конце концов, проникся, мне нравится итд. Но одновременно другая часть меня, расщеплённая, спрашивает: а нужно ли это на самом деле? Идея Хаскеля прекрасна и очень мощна - но не может ли быть так, что, заковав язык в эту прекрасную и действительно очень мощную идею, мы пришли к ситуации, когда для "обычного" вроде-как-но-не-совсем-императивного стиля программирования, без которого всё-таки не обойтись (и не только для ввода/вывода! нет, далеко не только!), нам приходится использовать столь сложные по своему уровню абстрактности и математической нетривиальности конструкции? Попросту говоря, не является ли энтузиазм по поводу Хаскеля и монад в определённой мере интеллектуальной мастурбацией, когда те, кто "проникся", радуются тому, как это умно и глубоко, и какие они крутые, что хорошо это понимают? А на деле для таких простых относительно вещей в языке не нужно было бы такой огород городить, если бы его получше построить, или снизить некоторую долю пуризма в его дизайне?

Это мысли по поводу Хаскеля; и реального ответа у меня нет пока, да может я завтра вообще решу, что полную чепуху написал (или пойму в очередной раз, что до сих пор не понимал монады). Но общая мысль, которую я из этого для себя вынес, пусть тоже тривиальна, но важно мне её для себя зафиксировать: не все трудности вызваны тем, что ты ещё не "привык к новому типу мышления". Не все (достаточно продвинутые, ясно) языки/способы программирования одинаково сложны, и дело лишь в том, что что-то тебе привычно, а что-то нет. Такое объяснение часто верно, и часто оказывается полезным и правильным, но оно не всегда верно, и об этом тоже надо помнить. Некоторые вещи просто действительно "объективно" тяжелее понять и выучить.


Как обычно, критика и комментарии приветствуются.
СсылкаОтветить

Comments:
[User Picture]From: avva
2006-02-28 08:30 pm

Re: оффтоп

да, я описывал вот здесь, если вдруг вы пропустили и интересно ;)
(Ответить) (Parent) (Thread)
[User Picture]From: vitaly_il
2006-03-01 04:40 am

Re: оффтоп

спасибо, обязательно почитаю! я действительно пропустил, т.к. провел три дня на ODSC без интернета (т.е. сеть, конечно, была, но у меня WiFi в лаптопе нет)
(Ответить) (Parent) (Thread)