Є кілька дуже хороших відповідей. Я спробую внести свій внесок у дискусію.
На тему декларативного, логічного програмування в Пролозі виходить чудова книга Річарда О'Кіф "Ремесло Пролога" . Йдеться про написання ефективних програм за допомогою мови програмування, яка дозволяє писати дуже неефективні програми. У цій книзі, обговорюючи ефективні реалізації декількох алгоритмів (у розділі "Методи програмування"), автор застосовує такий підхід:
- визначте проблему англійською мовою
- написати робоче рішення, яке є максимально декларативним; зазвичай це означає саме те, що ви маєте у своєму питанні, просто виправте Пролог
- звідти вживайте заходів, щоб удосконалити реалізацію, щоб зробити її швидшою
Найяскравіше (для мене) спостереження, яке я зміг зробити під час роботи над цим:
Так, остаточна версія реалізації набагато ефективніша, ніж "декларативна" специфікація, з якої розпочався автор. Це все ще дуже декларативно, лаконічно і легко зрозуміти. Між тим, що сталося між ними, це те, що остаточне рішення фіксує властивості проблеми, про яку початкове рішення не було в курсі.
Іншими словами, впроваджуючи рішення, ми використали якомога більше наших знань про проблему. Порівняйте:
Знайдіть перестановку списку таким чином, щоб усі елементи знаходились у порядку зростання
до:
Об'єднання двох відсортованих списків призведе до відсортованого списку. Оскільки можуть бути вже відсортовані підсписи, використовуйте їх як вихідну точку замість списків довжиною 1.
Невелика сторона: таке визначення, як те, що ви дали, є привабливим, оскільки воно дуже загальне. Однак я не можу уникнути відчуття, що цілеспрямовано ігнорує той факт, що перестановки є, ну, комбінаторною проблемою. Це те, що ми вже знаємо ! Це не критика, а лише спостереження.
Щодо справжнього питання: як рухатись вперед? Ну, один із способів - дати якомога більше знань про проблему, яку ми декларуємо на комп’ютері.
Найкраща мені спроба реально вирішити проблему представлена в книгах Олександра Степанова в співавторстві «Елементи програмування» та «Від математики до загального програмування» . Мені, на жаль, не підходить підсумовувати (або навіть повністю розуміти) все, що є в цих книгах. Однак підхід існує для визначення ефективних (або навіть оптимальних) бібліотечних алгоритмів та структур даних, за умови, що всі відповідні властивості вводу відомі заздалегідь. Кінцевий результат:
- Кожна чітко визначена трансформація - це уточнення обмежень, які вже існують (властивості, які відомі);
- Ми дозволяємо комп'ютеру вирішити, яке перетворення оптимальне виходячи з існуючих обмежень.
Що стосується того, чому це ще не зовсім сталося, ну, інформатика - це справді молода сфера, і ми все ще справляємося з по-справжньому цінуючи новизну більшості.
PS
Щоб дати вам смак того, що я маю на увазі під «вдосконаленням реалізації»: візьміть, наприклад, просту проблему отримання останнього елемента списку, в Prolog. Канонічне декларативне рішення полягає в тому, щоб сказати:
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|_]).
Останній елемент списку - це перший елемент зворотного списку.
Якщо ви хочете отримати непереконливу дискусію про те, що добре, декларативний Prolog, просто перегляньте деякі питання та відповіді в тезі Prolog на стеку Overflow .