Есть несколько очень хороших ответов. Я постараюсь внести свой вклад в обсуждение.
На тему декларативного, логического программирования в Прологе есть замечательная книга Ричарда О'Кифа «Ремесло Пролога» . Речь идет о написании эффективных программ с использованием языка программирования, который позволяет писать очень неэффективные программы. В этой книге, обсуждая эффективные реализации нескольких алгоритмов (в главе «Методы программирования»), автор придерживается следующего подхода:
- определить проблему на английском языке
- написать рабочее решение, как можно более декларативное; обычно это в значительной степени означает именно то, что у вас есть в вашем вопросе, просто исправьте Пролог
- оттуда, предпримите шаги, чтобы усовершенствовать реализацию, чтобы сделать это быстрее
Самое поучительное (для меня) наблюдение, которое я смог сделать, работая над этим:
Да, окончательная версия реализации намного эффективнее, чем «декларативная» спецификация, с которой начинал автор. Это все еще очень декларативно, кратко, и легко понять. Между тем произошло то, что окончательное решение улавливает свойства проблемы, о которой первоначальное решение было не замечено.
Другими словами, при реализации решения мы использовали как можно больше наших знаний о проблеме. Для сравнения:
Найти перестановку списка таким образом, чтобы все элементы были в порядке возрастания
чтобы:
Объединение двух отсортированных списков приведет к сортировке списка. Поскольку могут быть отсортированные списки, используйте их в качестве отправной точки вместо списков длиной 1.
Немного в стороне: определение, подобное тому, которое вы дали, привлекательно, потому что оно очень общее. Однако я не могу избежать ощущения, что он целенаправленно игнорирует тот факт, что перестановки являются, ну, в общем, комбинаторной проблемой. Это то, что мы уже знаем ! Это не критика, а просто наблюдение.
Что касается реального вопроса: как двигаться вперед? Ну, один из способов - предоставить как можно больше знаний о проблеме, которую мы объявляем компьютеру.
Лучшая попытка, которую я знаю, чтобы действительно решить эту проблему, представлена в книгах, написанных в соавторстве с Александром Степановым «Элементы программирования» и «От математики к общему программированию» . К сожалению, я не в состоянии подвести итог (или даже полностью понять) все в этих книгах. Однако подход заключается в том, чтобы определить эффективные (или даже оптимальные) библиотечные алгоритмы и структуры данных при условии, что все соответствующие свойства входных данных известны заранее. Окончательный результат:
- Каждое четко определенное преобразование является уточнением уже существующих ограничений (известных свойств);
- Мы позволяем компьютеру решать, какое преобразование является оптимальным, исходя из существующих ограничений.
Что касается того, почему этого еще не произошло, ну, информатика - это действительно молодая область, и мы все еще не можем по-настоящему оценить новизну большинства из них.
PS
Чтобы дать вам представление о том, что я имею в виду под «уточнением реализации»: возьмем, к примеру, простую задачу получения последнего элемента списка в Прологе. Каноническое декларативное решение состоит в следующем:
last(List, Last) :-
append(_, [Last], List).
Здесь декларативное значение append/3
:
List1AndList2
это объединение List1
иList2
Так как во втором аргументе append/3
у нас есть список только с одним элементом, а первый аргумент игнорируется (подчеркивание), мы получаем разделение исходного списка, который отбрасывает начало списка ( List1
в контексте append/3
) и требует, чтобы задняя часть ( List2
в контексте append/3
) действительно является списком только с одним элементом: так, это последний элемент.
Однако фактическая реализация, предоставленная SWI-Prolog , гласит:
last([X|Xs], Last) :-
last_(Xs, X, Last).
last_([], Last, Last).
last_([X|Xs], _, Last) :-
last_(Xs, X, Last).
Это все еще красиво декларативно. Читайте сверху вниз, там написано:
Последний элемент списка имеет смысл только для списка хотя бы из одного элемента. Таким образом, последний элемент для пары хвоста и заголовка списка: заголовок, когда хвост пуст, или последний из непустого хвоста.
Причина, по которой обеспечивается эта реализация, заключается в том, чтобы обойти практические вопросы, связанные с моделью исполнения Prolog. В идеале не должно иметь значения, какая реализация используется. Точно так же мы могли бы сказать:
last(List, Last) :-
reverse(List, [Last|_]).
Последний элемент списка является первым элементом обращенного списка.
Если вы хотите получить неокончательные дискуссии о том, что является хорошим, декларативным Прологом, просто просмотрите некоторые вопросы и ответы в теге Пролог на переполнении стека .