Monday, August 21, 2006

Декларативное программирование

В чем заключается индустрия software с технической стороны: программисты пишут программы, пользователи их покупают. Пользователям нравится, когда купленные за эти (зачастую немеряные) деньги программы работали. Желательно всегда.Потому что иначе пользователи начинают терять время и деньги, еще более немеряные. А если у пользователей деньги кончатся, программы они покупать перестанут.

Почему программы не работают? Очень просто. В программах есть баги. Баги доставляют программистам много-много увлекательных минут работы с отладчиком и кучу веселья (ну мне лично, по крайней мере ;), но баги все-таки юзеров очень раздражают, и багов поэтому должно быть поменьше и исправляться они должны побыстрее.

Что является основным источником багов? Нет, не программисты, поскольку программисты тоже люди, они совершают ошибки всегда. Однако ошибки совершать намного сложнее, когда работаешь с простой и насквозь понятной системой. А непонятной и запутанной системой software становится, когда в нем появляются разного рода побочные эффекты. То есть один раз нажимаем пимпу на диалоге -- все хорошо, второй раз нажимаем --программа падает. И, главное, программисту совершенно неясно, почему -- код тот же, данные входные те же(нажатие на кнопку) -- очевидный вывод: что-то где-то изменилось и все пошло не так. Что-то -- это какая-то глобальная переменная, состояние какого-то объекта и т.п. Вникать, что именно, программист будет доолго.

Знакомая ситуация?

Посмотрим теперь на другой аспект, процесс отладки в целом. Что происходит: тестер баг наш нашел, пожаловался и сидит радуется. Что должен сделать программист: повторить баг, под отладчиком, понять, куда ставить брейкпоинт, 250 тыщ раз нажать Step Over/Step Into, следя за тем, что и куда записалось, состояние какой переменной изменилось, и че из этого вышло. Причем связи все эти "что и куда", в программе, написаной на C#/Delphi/C++/Java, нифига не очевидны и хорошо просматриваются только в динамике, когда программа запущена. Программист тратит кучу времени.

А решение этих проблем -- это декларативные, а особенно функциональные языки. Когда все логические связи очевидны, по возможности статичны и строго типизированы. Это позволяет отловить огромную часть ошибок на этапе компиляции. Более того, в таких языках по сути отсутствует понятие порядка выполнения программы. Мы просто описываем связи между данными, преобразования, а интерпретатор вычисляет ответ. Поэтому не надо дебаггером проходить всю программу. Выяснить, какая именно связь не в порядке -- в декларативном языке в разы легче, и связь эту, как правило, можно очень просто отдельно протестировать (unit тесты как будто специально придуманы для ФЯ). 

Проблемы -- во-первых, не очень высокая производительность. Но это неважно в большинстве случаев, да и есть ФЯ Ocaml который может  поспорить по скорости с C++. Во-вторых -- и это главное -- learning curve тут намного круче. Особенно для программиста, не привыкшего к такому способу мышления. Сейчас, правда, они становятся все популярнее. C# 3.0 заимствует фичи из ФЯ,  функциональный язык Nemerle становится, кажется, самой модной темой на рсдн.ру.. 

Хотя я лично в то, что "скоро не будет ни кино, ни театров, а будут сплошные ФЯ" не верю. Мейнстрим это все-таки промышленный дешевый процесс разработки software руками тысяч гастарбайтеров -- индусов, китайцев, и, может быть, русских. :) Индустриализация, промышленный переворот, от кустарного производства -- к заводам и фабрикам ПО :)
Тем не менее, сложные задачи решать на ФЯ намного удобнее. И самая большая з/п (спускаясь на землю :) -- есть и будет у людей с редкими навыками в специфических областях индустрии. Примеры успешного применения ФЯ покажу потом :)

8 comments:

voidbent said...

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

lrrr said...

Да нет, щас вот в гугле специально потыкался -- все его таковым считают. Что и логично -- декларативное(как оно почти везде определяется) подразумевает описание проблемы без явного описания алгоритма решения, в виде последовательности действий. ФЯ вполне сюда подпадают.

Anonymous said...

По-моему, стоит различать эти понятия. Декларативное - это всякие там атрибуты, top-level синтаксические макросы а ля Немерле и. т.п.

Функциональное - это когда функции являются first-class values.

Да, ФЯ может иметь декларативные средства, но эти понятия не взаимозаменяемы.

lrrr said...

Я понимаю что на википедию ссылаться это не комильфо.. ;)
но все-таки:
en.wikipedia.org/wiki/Declarative_programming

"a program is "declarative" if it describes what something is like, rather than how to create it"

В большинстве статей ФЯ относят к декларативным языкам, то есть я как-то даже не встречал, чтоб не относили :)

Anonymous said...

"a program is "declarative" if it describes what something is like, rather than how to create it"

И как это связано с ФП?

Кстати, там же есть и другое определение:

According to a different definition, a program is "declarative" if it is written in a purely functional programming language, logic programming language, or constraint programming language. The phrase "declarative language" is sometimes used to describe all such programming languages as a group, and to contrast them against imperative languages.

Т.о., под декларативным языком можно понимать и неимперативный, о чём ты и говоришь :) .

Anonymous said...

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

Anonymous said...

Однако декларативное программирование - это только полумера. И на C++ написали симпатишный декларативный парсер EBFN-грамматики, только от этого программы на С++ менее глючными не станут.
Для меньшего количества глюков необходима еще куча вещей:
* garbage collector - меньше memory leak-ов
* интерпретатор - чтобы быстрее проверять работоспособность кода
* отсутствие адресной арифметики и проверка обращений к памяти - чтобы ее не засрать
* отсутствие NULL-а - чтобы к нему не обращаться
* наличие exceptions по каждой ошибке - чтобы программа сразу падала, а не через полгода
* traceback - чтобы тебе сказали - где упало (хотя core.dump тож ниче)
* ... и многое другое

А отсутствие shared mutable state - это скорее архитектурные вопросы. И в хаскеле можно все загадить IORef-ами :)

lrrr said...

Да, это ведь все очень сильно связанные вещи -- строгая система типов, отсутствие указателей, gc.. В ФЯ это красиво и органично вписывается.

Да, неплохо также иметь "правильные" механизмы синхронизации и flow control, сообщения, continuation'ы. Они в ФЯ тоже очень хорошо и понятно работают.

Всякие там spirit'ы и phoenix'ы -- по мне так скорее это proof-of-concept, что в C++ тоже можно что-то похожее изобразить.