суббота, 20 марта 2010 г.

Complexity and order

Современные программы бывают весьма непростыми, поэтому языки программирования содержат средства для борьбы со сложностью. Одним из основных источников сложности, являются зависимости между разными частями приложения. Подобного рода зависимости усложняют внесение изменений в код. Именно этим объясняется популярность ООП - средства борьбы со сложностью подобного рода. Однако зависимости между разными частями программы - не единственный источник сложности. Помимо этого, очередность выполнения тех или иных действий, может зависеть от каких либо факторов. Например, операция чтения из файла должна выполняться строго после его открытия и до закрытия. Это вносит сложность, так-как вызов метода записи в файл, зависит от того, были ли вызваны другие методы. Все это становится особенно очевидным, при написании многопоточных и асинхронных приложений. Функциональное программирование, по моему скромному мнению, как раз и является отличным средством для борьбы со сложностью подобного рода. Ведь чаще всего нас не интересует то, в каком порядке выполняются различные действия, нам нужен частичный порядок, а не всеобщий. ФП позволяет решать задачу в декларативном стиле, не определяя явно порядок вызова функций, если это не нужно. Поэтому для меня очевидно, что будущее именно за теми языками программирования, которые позволят применить одновременно как объектно ориентированный, так и функциональный подход к решению сложных задач.

19 комментариев:

v s комментирует...

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

Lazin комментирует...

Я имел ввиду не алгоритмическую сложность, а сложность в общем. Сложность понимания кода, сложность внесения изменений и тд.

v s комментирует...

Какая может быть сложность внесения изменений, неужели редактор не может справится?
Если человек не может представить концептуальную модель объекта проектирования, то ему ни одна парадигма не поможет.

legolegs комментирует...

Будущее (отдалённое) - за декларативными языками. А ООП и функциональщина - всего лишь две дорожки одной развилки этого пути.

Анонимный комментирует...
Этот комментарий был удален администратором блога.
Lazin комментирует...

2v_s: бывают очень сложные задачи, зачем по вашему люди придумали всевозможные паттерны проектирования?
Писали-бы себе на Си, раз все так просто :)

2legolegs: согласен, по большей части )

v s комментирует...

Паттерны не придуманы, паттерны найдены. Паттернов много: рефакторинга, интеграции, проектирования. Какие вы имели в виду?
Декларативные языки далеко не к каждой задаче подходят.

Lazin комментирует...

Я же написал - паттерны проектирования. Многие из паттернов GoF просто не нужны, когда пишешь на функциональном ЯП, первое что пришло в голову - шаблоны flyweight, или command, ленивость и ФВП избавляют навсегда от их применения.

v s комментирует...

Избавляясь от этого паттерна мы избавляемся от сложности?

Lazin комментирует...

Я не это хотел сказать, а то, что эти паттерны появились по причине отсутствия в С++/Java(не знаю какой язык использовали Гамма и компания) поддержки ленивых вычислений. Отсюда растут ноги у шаблона "приспособленец". О многих других можно сказать подобное.

v s комментирует...

Часто вы использовали ленивые вычисления?
А часто ли вы видели в живом коде "Приспособленца"?
Те кто не подозревает о существовании ленивых вычислений, насколько для него код будет сложнее чем для вас?

Lazin комментирует...

Часто вы использовали ленивые вычисления?
Изрядно, на С++ это обычно сводится к написанию своего итератора.

А часто ли вы видели в живом коде "Приспособленца"?
Может у меня просто задачи такие, но довольно часто. А использовать этот шаблон начал еще до того как узнал о его существовании :)

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

А вообще, пост немного о другом.

v s комментирует...

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

also комментирует...
Этот комментарий был удален администратором блога.
Lazin комментирует...

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

Во первых, я имел ввиду не алгоритмическую сложность, а сложность системы в общем.
Если программа состоит из множества зависящих друг от друга частей, то чем больше таких зависимостей, тем сложнее система.
Тоже самое можно сказать о порядке выполнения операций. Чем больше в программе инвариантов вида - "операция Б может быть выполнена только после операции А, но до операции С", тем сложнее система.
Языки программирования, поддерживающие ООП - позволяют уменьшить количество зависимостей между разными частями приоложения, поэтому, говорят, что они позволяют бороться со сложностью(не той, которая big-O).
ФП позволяет не определять явно порядок выполнения операций, уменьшая тем самым количество зависимостей другого рода, поэтому оно тоже позволяет бороться со сложностью.
Смысл поста сводится к тому, что противопоставлять ФП и ООП - дело не благодарное.

Можно живой пример на обоих языках, доказывающий вашу точку зрения?
Здесь можно посмотреть на реализацию алгоритма quicksort.
Реализация этого алгоритма на haskell выглядит так:
qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)
когда мне в последний раз понадобилось вспомнить как работает этот алгоритм, я просто вспомнил, как он выглядит на haskell - настолько здесь все декларативно и емко :)

v s комментирует...

http://insanegigolo.livejournal.com/58673.html#cutid1

Lazin комментирует...

2v_s, цикломатическая сложность разных реализаций одного алгоритма - всегда будет одинакова.
Нужно учитывать и то, что мы обычно не записываем программы в виде цепочки вычислений с кучей ветвлений, вместо этого, мы обычно руководствуемся принципом - "разделяй и властвуй", делим код на независимые части и компоненты, в результате чего, его становится проще понимать. А еще, в когда пишешь на языке с поддержкой ФП, не каждый control path задается явно, ведь ФП программа, это не последовательность statement-ов, а просто множество выражений, последовательность вычисления которых не задается программистом в явном виде.
Судя по моему опыту программиования на F#, кода получается намного меньше, и он намного проще для понимания :)

errorka комментирует...

http://insanegigolo.livejournal.com/58673.html#cutid1

Lazin комментирует...

Часто вы использовали ленивые вычисления?
Изрядно, на С++ это обычно сводится к написанию своего итератора.

А часто ли вы видели в живом коде "Приспособленца"?
Может у меня просто задачи такие, но довольно часто. А использовать этот шаблон начал еще до того как узнал о его существовании :)

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

А вообще, пост немного о другом.