Как стать автором
Обновить

Можно ли использовать декларативный и императивный стили написания программ одновременно?

Время на прочтение9 мин
Количество просмотров8.4K
Всего голосов 8: ↑5 и ↓3+3
Комментарии31

Комментарии 31

А вы не смотрели в сторону языка Qml?

Там неплохо совмещены концепции декларативности и императивности

Смотрел и даже использую. Но QML это скорее язык разметки с программными вставками, а не настоящий язык программирования.

Посмотрите в сторону OCaml.

Посмотрите в сторону OCaml

А где там декларативная парадигма?

Я не спец по OCaml, но про него пишут, что он вроде чисто императивный.

Я больше про смешение парадигм. OCaml позиционирует себя как функциональный, императивный и объектно ориентированный язык.

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

Спасибо за пояснение! Обязательно посмотрю.

Лучше тогда сразу F#, он от Ocaml и произошел.

F# позволяет писать не только в функциональном стиле, но и в императивном, и напрямую C#-ом.

А помните, были такие "Компьютеры пятого поколения"? Это такая история про то, как Пролог и несколько миллиардов иен (aka полмиллиарда долларов) ухнули в трубу в 80е годы.

Одна из причин уха была банальной - чтобы надекларировать таким образом серьезную бизнес задачу (не демку, а нормальное такое ТЗ листов на 30-50), и при этом сохранить рассудок всех причастных, нужны какие-то средства управления сложностью и декомпозиции (вроде как ООП с этим кое-как справляется, со скрипом правда), а также люди с декларативно-функциональным мышлением, которых мало. (Потому что такому ремеслу учат на матфаке, а сажать на разработку магазина детских сосок Ph.D, который напишет диссертацию "Алгебраизация сосок. Оператор композиции бутылочки и молочка." - очень накладно. За эти деньги можно нанять десяток слесарей-императивщиков "бери ближе, швыряй дальше").

Автоматизировать создание правил на практике не представляется возможным, потому что теорема Геделя о неполноте формальных систем встает на пути like a boss (Ну или как Гендальф и его YOU SHALL NOT PASS!). Японцы погорели и на этом в том числе - их системы умнели-умнели, а потом внезапно тупели.

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

Это ни в коем случае не говорит о том, что ваша идея плохая. Но вы ходите по чрезвычайно тонкому льду. И когда (и если!) он провалится, под ним вас будут ждать те самые японцы, а также компьютер с аппаратной реализацией LISP на компонентах с датам 80х годов прошлого века.

Я понимаю, что лед тонок, но иногда хочется найти что-то большое и светлое, хотя в конечном итоге все действительно может закончится как у "тех самых японцев".

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

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

Спасибо за развернутый комментарий!

Ну и пять копеечек про ООП. Как ни странно:

1) В нем таки есть декларативная часть, все эти pubic, private, virtual, etc
2) Эта декларативная часть, внезапно, элегантно позволяет реализовывать декомпозицию и управление сложностью.
3) При наличии перегрузок, можно поиграть в алгебраическое мышление, рассматривая взаимодействие двух объектов как бинарную операцию.

Ух ты, ООП - это обкатанная практикой технология, которая совмещает декларатив с императивом и вроде позволяет освоившим ее вкусно кушать и мягко спать!

Хм, а ведь точно! Любой интерфейс, это действительно декларативная часть императивного языка!

Реально, большое спасибо! Ответ на один из вопросов оказался даже проще и без необходимости писать код!

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

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

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

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

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

Декларативный подход фокусируется на задаче. Для разных задач может подходить одно и то же решение. Если описать задачу, используя абстрактный инструментарий, то можно применять готовые решения. Скрытый решатель не является неотъемлемой частью декларативного подхода. Он скорее расширяет сферу применения конкретной реализации конкретного языка. И в декларативном языке возможность представления разных типов и классов задач определяет вид программы.

Формальное комбинирование подходов, такое как упомянутые ключевые слова для ООП и SQL с императивными командами, несомненно возможно. Но не стоит усилий в силу избыточности. Если язык позволяет выразить задачу исключающим ошибочность описания(что само по себе нетривиально для многих типов задач) способом и предоставляет инструменты вывода решения из готовых или комбинацией готовых, то возможность реализовать отдельное частное решение в этом же языке с использованием тех же или других конструкций уже лишнее. Получится или два языка в одном, или крайне неудобный универсальный инструмент.

Формальное комбинирование подходов, такое как упомянутые ключевые слова для ООП и SQL с императивными командами, несомненно возможно. Но не стоит усилий в силу избыточности.

Совмещение SQL с императивными командами давно стало промышленным стандартом - PL/SQL

Можно подумать, вы не видели лямбда-исчисление (куда уж декларативнее?) и доказательство его полноты по тьюрингу.

Т.е. технически одно через другое делается легко (мне очень нравится хаскелевский подход, когда функциональная программа на лету вычисляет императивную, которая и взаимодействует с миром), вопрос лишь в том, как грамотно разделить – что удобней делать декларативно, что императивно. Так, чтобы потёкшая императивщина (mutable state, вот это всё...) не сломала декларативную часть.

НЛО прилетело и опубликовало эту надпись здесь

Как по мне, так важнее не спецификация языка (хотя плохо, если язык ограничивает разработчика), а сам подход.

Каждая парадигма наиболее хорошо описывает определённый круг задач. Например:
ООП хорошо моделирует бизнес-домен
Императивно хорошо описывать пошаговые алгоритмы
Декларативно удобно описывать задачу
Функционально - математику.

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

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

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

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

После прочтения статьи каша в голове. Императив - это основа, если речь идёт об ИТ, это тот базовый, фундаментальный уровень на который вы неизбежно скатитесь, когда откомпилируете программу в машинные коды и отправите на исполнение. Так что все прочие парадигмы являются дочерними, если вам требуется конкретное исполнение (реализация). Даже в математике, когда вам требуется конкретный ответ (цифра), вы скатываетесь до конкретной последовательности вычислений.

Императив - это основа, если речь идёт об ИТ, это тот базовый, фундаментальный уровень на который вы неизбежно скатитесь

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

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

Диалектика? Ну да, конечно! Переход количества в качество. Отрицание отрицания. Я просто хотел указать именно на то, что многие этого не понимают и рассматривают парадигмы как нечно отдельное. То есть они дошли до отрицания предыдущей стадии, когда отвергают императивную парадигму как основу для возникнования декларативной парадигмы, а до отрицания отрицания они ещё не дошли.

А Вы знакомы с языками логическо-функционального программирования, такими как Curry или Mercury? Мне кажется, они близки к тому, что Вы хотите предложить. Они позволяют совмещать описания функций и логических конструкций, а также в определенных случаях логически выводить входные аргументы функции, если известен результат ее вычисления.

Такие конструкции как

(условие1) -> {действие1} (условие2) -> {действие2} (условие3) -> {действие3} -> {действие_иначе};

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

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

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

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

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

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

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

Например, создатели языка Oz/Mozart пытаются решить эту проблему оборачиванием переменных в специальную структуру под названием Cell и заменой стека вызовов функций древовидной структурой под названием Computational Spaces.

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

Можно ли использовать декларативный и императивный стили написания программ одновременно?

Даже до ООП, просто описывая интерфейс функции на С, вы уже декларируете. Другое дело, что этой декларации недостаточно для полного описания функционала и соответственно, возможности автоматической реализации, но и языки позволяющие такое описание делать есть, к примеру `Idris`. К сожалению, пока там всё сложно, по слухам даже правильное описание функции `sort` долго сделать не могли.

Я не совсем понял претензию к SQL

Тем же Select - вы говорите: дай мне это отсюда в таком-то порядке отфильтровав таким-то образом. При этом как это будет происходить вас не волнует. Если бы вы писали в императивном стиле - вы бы уже должны были думать о том, какая там структура данных для хранения, как будете обходить, фильтровать хранить таблицы, объединять и т.д.

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

У меня нет претензий к SQL. Я писал о том, что сама команда "дай", по сути императивна.

Концепция "условие-действие" реализована в языках МЭК 61131-3 для программируемых логических контроллеров (ПЛК). Более того, микропроцессоры для ПЛК имеют систему команд, изначально заточенную под концепцию "условие-действие".

Зачем эта концепция нужна? Для написания программ с параллельным действием. Процессор пробегается по всем условиям и выполняет действия тех условий, которые выполняются. То есть фактически программный счетчик (Program counter) уже не имеет такого значения как обычно, так как за один цикл порядка 1-10 мс он пробегается по всем (почти) строкам "условие-действие". То есть за 1-10 мс может выполниться от 0 до N действий в зависимости от состояния. Программы с параллельным действием легче писать, каждое условие позволяет легко контролировать эти действия.

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

Есть язык в котором можно добавлять всякого рода синтаксические конструкции вполне законно. Forth называется.
Есть наработки порождающие его расширенную версию BacFORTH. Эта версия имеет все конструкции Пролога.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий