Я не знаю... А кто это может знать? Приступая к разработке какого-либо сложного "куска кода" я слишком мало знаю о нём, что бы обоснованно судить какая парадигма лучше всего подходит. А пишу криво и много выкидываю, затем переделываю, снова и снова. И вот, после многократного рефакторинга, после многих дней или недель работы над проблемой, когда я уже знаю очень много и о проблеме и об коде, который написал, у меня может появиться мнение о предпочтительной парадигме, не в результате предварительного анализа, но в результате естественного развития кода.
В начале SICP приводится мнение, что
Программы пишутся, чтобы люди их читали, и лишь иногда - чтобы машины их выполняли- я думаю, что оно немного устарело. Программы пишутся для того, чтобы люди их редактировали. Если бы программы писались лишь один раз, то, вероятно, стоило бы рассуждать о преимуществах функциональной или императивной парадигмы. Но если вы считаете, что разработка это, прежде всего, процесс экспериментирования и переписывания (рекомендую яркое эссе Пола Грэма на эту тему: "Хакеры и художники"), то проблематика выбора парадигмы отходит на второй план, гораздо важнее другие вещи: насколько легко вы можете внести в программу изменения и увидеть их результат? или насколько легко вы можете диагностировать ошибку?
Common Lisp удивительно хорош не только тем, что сочетает в себе различные парадигмы, что даёт возможность осуществить смену парадигмы для конкретного куска кода без смены языка, но и тем, что значительная часть языка посвящена облегчению процесса программирования (хакерства - в терминологии Пола Грэма), не облегчения кодирования известного алгоритма (хотя и в этом с ним мало кто может соперничать), но прежде всего поддержке процесса поиска решения. И благодаря именно этим возможностям возможен такой мощный инструмент, как SLIME. Пара свежих примеров из собственной практики:
- Порой хочется пропатчить какую-нибудь библиотеку, что бы изменить поведение какой-либо функции. Просто для проверки, как изменится поведение моего кода. Либо порой может оказаться полезным добавить в библиотечную функцию диагностическое сообщение. При использование многих других языков это довольно проблематично, надо сделать очень много действий и обычно, без крайней необходимости, лучше такого не делать. В Common Lisp при использование SLIME это настолько тривиально, что я пользуюсь этим приёмом постоянно, надо то: установить курсор в месте вызова функции, нажать C-M-. - в результате откроется код нужной функции, с помощью C-x C-q переключить флаг read-only (это даст возможность редактировать буфер, но не сохранить на диск), исправить код и теперь С-M-x перекомпилирует изменённую функцию, всё, можно смотреть как изменилось поведение программы
- Сайт lisper.ru иногда зависает. Это случается достаточно редко и у меня до сих не было возможности диагностировать ошибку. Но вот вчера сайт снова завис, а я имел немного свободного времени, что бы разобраться. Выяснение точного места в коде, приводящего к этому заняло менее 5 минут: с помощью SLIME подключился к серверу (который находится в другой стране), получил список потоков, выбрал поток "Hunchentoot listener" и выполнил команду slime-thread-debug, что привело к выводу стека вызовов зависшего потока, я увидел необработанное исключение и в течении минуты установил точное место в коде hunchentoot, где оно возникает и не получает должной обработки (надо заметить, это довольно редкая проблема, по крайней мере, я сталкиваюсь с ней только на одном сервере и то, иногда).
В общем, я хорошего мнение о функциональной парадигме как таковой, но совершенно против языков подобных Haskell.
Согласен на все сто.
ОтветитьУдалитьЕсть же куча алгоритмов/задач где ФП трудно приложить, например сортировка "на месте".
Мне Haskell тоже кажется каким-то насилием над мозгом и я предпочту ему OCaml.
Хаскель заставляет думать над задачей, потому что в нём для впихивания состояния и ввода-вывода (т.е. усложнения кода) надо сделать вполне определённые усилия, что мешает делать это просто так, только когда есть основания. В итоге это сводится к тому, что код не даёт себя усложнять излишне, а думание над ним нередко приводит к упрощению решения.
ОтветитьУдалитьЯ сомневаюсь, что можно избавиться от процесса размышления над задачей, думать надо, но возможность неосознанно усложнять код состояниями и нелокальными переходами лишь откладывает проблемы недодуманного решения на потом.
Этот комментарий был удален автором.
ОтветитьУдалитьto VoidEx
ОтветитьУдалитьВот, допустим, нужно мне сделать в чистой функции отладочную печать. Haskell мне этого сделать не даст и я буду вынужден добавить IO. А затем и во все чистые функции которые эту вызывают. Отладка программы на Haskell превращается в ад.
VoidEx, хаскел конечно крут, но когда нужно внести изменения в уже работающий код, то есть вероятность наступления полного П, который приводит к лютому перекоблашиванию типов данных и половины алгоритмов.
ОтветитьУдалитьПо статье с автором совершенно согласен. Для себя я уже определил, что главное в лиспе - это свобода.
> В итоге это сводится к тому,
ОтветитьУдалить> что код не даёт себя усложнять излишне
Угу, тут читаю книжку по Rails (ради образования, что бы знать с чем это едят), и там встретил термин "синтаксический уксус" - насколько я понял, достаточно известный термин в среде RoR. Идея всё та же: помешать писать плохо. Я сразу вспомнил С++, все эти public, protected, private, которые как бы тоже должны мешать писать плохо, а в итоге, рождается куча проблем, чрезмерная сложность, а полезной отдачи от этих свойств я в своих проектах не припомню (а писал я на С++ немало).
Мне не нужны искусственные преграды, выставленные разработчиками языка, я в состоянии сам разобраться что плохо, а что хорошо и как мне лучше писать свой код.
to andy128k
ОтветитьУдалитьдля отладки есть Debug.Trace
to Valeriy
К сожалению, не могу сравнить, по ощущениям, переколбашивать мне приходилось не чаще, проблем с этим _пока_ не встречал, а там посмотрим.
Что действительно ощущается, так это то, что иногда над буквально 10-ю строками думать надо, не получается сходу наваять. Иногда, когда решение получается жутким, я размышляю, как бы я решил эту же задачу на C++/C#/etc., и прихожу к выводу, что не лучше (только длиннее раза в 3-4), разве что написать было бы чуть легче (из-за возможности впихивать состояния без отражения этого в типизации), но проблемы от этого не исчезают, т.е. само решение плохое, а не язык. Беда в том, что на C++/C# такое кривое решение даже не выглядит кривым, оно нормально и типично, в Хаскеле же это противоестесственно. Лично мне это наоборот нравится. Bondage&Discipline, так сказать.
Это, разумеется, субъективные ощущения.
to voidex:
ОтветитьУдалитьСравнения с C++/C#/etc (инетерсно, что имеется в виду под etc) не интересно, это очень удобные мишени ;) лучше сравнивать с Perl/Python/Common Lisp/etc,
to archimag
ОтветитьУдалить> Я сразу вспомнил С++, все эти public, protected, private, которые как бы тоже должны мешать писать плохо, а в итоге, рождается куча проблем, чрезмерная сложность, а полезной отдачи от этих свойств я в своих проектах не припомню
Ну так не надо по одной неудачной попытке обобщать. В Си++ я тоже не припомню полезной отдачи.
> Мне не нужны искусственные преграды, выставленные разработчиками языка, я в состоянии сам разобраться что плохо, а что хорошо и как мне лучше писать свой код.
Подозреваю, что вам нравится динамическая типизация?
> Подозреваю, что вам нравится динамическая типизация?
ОтветитьУдалитьУгу. Я начинал изучать Common Lisp и Haskell в одно и то же время, когда работал над одной очень сложной проблемой, и даже склонялся в пользу Haskell именно из-за его более мощных (в функциональном плане) свойств. Но, это была действительно сложная проблема, первая версия алгоритма (в коем было в итоге менее 1000 строк кода) родилась только через два месяца и Haskell до этого срока не дожил. Я не знал как решить задачу, и пробовал разные варианты - Haskell для такой работы просто не подходит.
to archimag
ОтветитьУдалить> лучше сравнивать с Perl/Python/Common Lisp/etc
В данном контексте нет разницы, я сравниваю с языками без функциональной чистоты.
Неявное же введение переменных и динамическая типизация в Python хорошо сказывается при попытке изменять существующий код. В Хаскеле мне как раз жутко нравится невозможность где-то что-то исправить так, чтобы программа стала неверной, но скомпилировалась.
Можно сказать, что настоящие мачо не ошибаются, но я в это смутно верю и больше доверяю type checker'у, тем более что это даёт мне возможность не растрачивать внимание на то, что может быть автоматизировано, а думать над задачей.
> Но, это была действительно сложная проблема, первая версия алгоритма (в коем было в итоге менее 1000 строк кода) родилась только через два месяца и Haskell до этого срока не дожил.
Не понял, на чём была первая версия и каким образом не дожил Хаскель?
> Я не знал как решить задачу, и пробовал разные варианты - Haskell для такой работы просто не подходит.
Хотелось бы узнать, какие проблемы были с Хаскелем. Чисто практический интерес.
> В данном контексте нет разницы,
ОтветитьУдалить> я сравниваю с языками без функциональной чистоты.
Есть огромная разница. Ибо утвержедние, что решение получается в 3-4 раза короче не может быть применимо ко всем языкам "без функциональной чистоты".
> Не понял, на чём была первая версия и каким образом
> не дожил Хаскель?
Common Lisp, хаскель просто оказался неудобен в использовании и был благополучно забыт.
> нравится невозможность где-то что-то исправить так,
> чтобы программа стала неверной
> Хотелось бы узнать, какие проблемы были с Хаскелем.
> Чисто практический интерес.
В этом корень проблем. В Common Lisp я не обязан выполнять всю программу целиком. Я спокойно тестирую в REPL отдельные её кусочки, изменяю их так, что программа нарушается, но я могу сразу понять как изменяется поведение конкретной функции и если это то, что мне надо, то тогда я уже переделываю программу. Мне не надо переделывать всю программу только для того, что бы убедиться в том, что моя идея была неверной, я могу увидеть это сразу.
to archimag
ОтветитьУдалить> Есть огромная разница. Ибо утвержедние, что решение получается в 3-4 раза короче не может быть применимо ко всем языкам "без функциональной чистоты".
Это была не главная мысль. Главное, что решение не лучше. Имеется в виду не то, что на других языках лучшего решения не получить (это, конечно, чушь), а то, что отсутствие навязанных Хаскелем ограничений решению не помогает.
> В Common Lisp я не обязан выполнять всю программу целиком. Я спокойно тестирую в REPL отдельные её кусочки, изменяю их так, что программа нарушается, но я могу сразу понять как изменяется поведение конкретной функции и если это то, что мне надо, то тогда я уже переделываю программу
А в чём проблема с REPL у Haskell'я? Я, собственно, всегда сижу с запущенным GHCi.
> А в чём проблема с REPL у Haskell'я
ОтветитьУдалитьВ том, что программа должна оставаться корректной.
> В том, что программа должна оставаться корректной.
ОтветитьУдалитьВ процессе разработки все неугодные места можно заткнуть undefined и тестировать недоделанную версию. Я в процессе написания все неочевидные места или те, на которых мне в данный момент не хочется заострять внимание, заменяю undefined, зато всегда уверен, что ошибок типизации нет, а в Хаскеле это уже много значит. Ну а как только каркас написан, его уже можно и тестировать, даже при наличии undefined, главное, в готовой программе их не оставлять, но это и так понятно.
to viodex:
ОтветитьУдалитьРазговор малость отошёл от темы статьи :)
Я когда тоже любил статическую проверку типов и доказывал, что она совершенно необходима. Но это было давно и мой способ разработки программ изменился. Я как бы достаточно много писал на разных языках и часто был вполне доволен (или даже счастлив) результатом. Я большинство известных парадигм (включая функциональную) и не могу сказать, что какая-то из них оказалась более выдающейся, чем другие.
Пост, собстенно, о том, что для разработки программ парадигма на самом деле не так уж и важна (можно использовать любую), а колличество строк кода вообще мало о чём говорит, реально важны другие вещи. И мне не доводилось услышать аргументов в пользу ФП, которые бы проясняли важные для меня моменты.
to archimag:
ОтветитьУдалитьНу от темы-то отошли, потому что спорить не с чем, я с мыслью согласен. Кол-во строк важно, но совсем не потому, что их надо писать (я и тысячами на Си++ в своё время мог строчить), их надо читать, понимать, и модифицировать. Тут становятся важным лёгкость понимания (иногда я наоборот пишу вариант подлиннее, лишь бы он выглядет понятнее, как выражение мысли), и изменения.
Как я неоднократно упоминал, Хаскель провоцирует писать более декларативно, это ему в плюс. В плане модифицирования тоже плюс, тут играет роль и типизация, и такие, казалось бы, мелочи, как то, что конструкторы надо писать с заглавной (тогда foo Bar = 34 никогда не воспримется так же, как foo bar = 34, т.е. стерев конструктор Bar мы не рискуем превратить паттерн в наиболее общий).
Однако есть и минус. Любители писать монады забывают про монадические трансформеры, а язык никак не заставляет их писать. В итоге при желании впихнуть логирование в функцию бинарной сериализации я наткнулся на необходимость написать этот трансформер самому, хотя при наличии оного проблем бы не возникло, изменения небольшие и делаются просто.
По поводу REPL я, вроде, написал. Проблем с изменением готового кода и использованием его в полусобранном состоянии нет, мощная типизация исключает множество ошибок.
Правда, это аргументы не в пользу чистого ФП со статической типизацией, а не ФП как такового, но, собственно, именно функциональную чистоту и типизацию я считаю важной, а не сам стиль.
to voidex:
ОтветитьУдалитьПосмотрел я на Debug.Trace. Этож признание в собственной беспомощности. Этот unsafePerformIO -- не что иное как хак.
to voidex:
ОтветитьУдалитьНу я таки так и не понял, как мне понять причину, если мой удалённый сервер, написанный на Haskell, вдруг зависнет (во избежание флэйма на тему надёжности функциональных программ, сбой происходит на уровне системного вызова, очень редко)?
Или я вот пишу для веб на Common Lisp, на одном мониторе у меня код, а на другом веб-страница. Я слегка изменяю код, нажимаю C-M-x для перекомпиляции изменённой функции и, без перезагрузки веб-сервера, без каких-либо дополнительных действий, на другом мониторе тут же смотрю изменения. (почти как в php :)) могу я так писать на haskell?
to andy128k:
ОтветитьУдалить> Этож признание в собственной беспомощности. Этот unsafePerformIO -- не что иное как хак.
unsafePerformIO (как и unsafeInterleaveIO) не хак, unsafe он потому, что снимаются гарантии (чистота и порядок выполнения соответственно), за которые теперь ответственен программист. Он там как раз затем, чтобы протрейсить, как работает _чистый_ код (т.е. без повторных вычислений, без определённого порядка), т.е. как раз в отладочных целях. Там он вполне уместен.
to archimag:
А какое отношение поставленные вопросы имеют к языку? Удаленная отладка, вон, в C++ есть, уж не заслуга ли это языка и императивного подхода? Вас интересует теоретическая возможность горячей замены кода? Она есть. Ссылок не откопаю, но вроде кто-то писал proof-of-concept. Непонятно, почему бы ей не быть (возможности).
Т.е. ответ: да, можете. Разумеется, при наличии выбора следует обращать внимание не только на сам язык, но и на библиотеки/инструментарий. В общем-то, оба ваших вопроса как раз не о языке как таковом. Для Хаскеля готовой, реализующей данный функционал, нету; но это не недостаток самого языка и уж тем более ФП подхода. Erlang с этим всем справляется, там готовенькое есть.
to viodex:
ОтветитьУдалить> А какое отношение поставленные вопросы имеют к языку?
Язык может иметь блестящую спецификацию и безукоризненное математическое обоснование (как, например, Haskell), но на нём может оказать очень неудобно программировать реальные задачи. Я же не случайно упомянул в основном тексте о том, что заметная часть стандарта Common Lisp направлена на поддержку процесса разработки. Т.е. разработчики языка думали не только о том, насколько эффективно можно реализовать тот или иной алгоритм на CL, но и о том, как максимально упростить процесс разработки, какие инструментальные средства можно построить вокруг языка. В итоге, я очень сильно сомневаюсь, что для Haskell вообще возможно создание инструмента, хоть в какой-то мере сопоставимого со SLIME. В общем, это и отличает язык, созданный специально для решения сложных практических задач (Common Lisp), от языка, стремящего предоставить максимально чистую реализацию математической концепции (Haskell).