Có một số câu trả lời rất tốt. Tôi sẽ cố gắng đóng góp cho cuộc thảo luận.
Về chủ đề khai báo, lập trình logic trong Prolog, có cuốn sách tuyệt vời "The Craft of Prolog" của Richard O'Keefe . Đó là về việc viết các chương trình hiệu quả bằng ngôn ngữ lập trình cho phép bạn viết các chương trình rất kém hiệu quả. Trong cuốn sách này, trong khi thảo luận về việc triển khai hiệu quả một số thuật toán (trong chương "Phương pháp lập trình"), tác giả đã thực hiện cách tiếp cận sau:
- xác định vấn đề bằng tiếng Anh
- viết một giải pháp làm việc càng khai báo càng tốt; thông thường, điều đó có nghĩa khá chính xác những gì bạn có trong câu hỏi của mình, chỉ cần sửa Prolog
- từ đó, thực hiện các bước để tinh chỉnh việc thực hiện để làm cho nó nhanh hơn
Quan sát giác ngộ nhất (đối với tôi) tôi có thể thực hiện trong khi thực hiện theo cách này:
Có, phiên bản cuối cùng của việc triển khai hiệu quả hơn nhiều so với thông số kỹ thuật "khai báo" mà tác giả bắt đầu. Nó vẫn rất khai báo, cô đọng và dễ hiểu. Điều đã xảy ra ở giữa là giải pháp cuối cùng nắm bắt được các thuộc tính của vấn đề mà giải pháp ban đầu bị lãng quên.
Nói cách khác, trong khi thực hiện một giải pháp, chúng tôi đã sử dụng càng nhiều kiến thức về vấn đề càng tốt. Đối chiếu:
Tìm một hoán vị của một danh sách sao cho tất cả các phần tử theo thứ tự tăng dần
đến:
Hợp nhất hai danh sách được sắp xếp sẽ dẫn đến một danh sách được sắp xếp. Vì có thể có các danh sách con đã được sắp xếp, hãy sử dụng chúng làm điểm bắt đầu, thay vì danh sách phụ có độ dài 1.
Một khía cạnh nhỏ: một định nghĩa giống như định nghĩa bạn đã đưa ra là hấp dẫn bởi vì nó rất chung chung. Tuy nhiên, tôi không thể thoát khỏi cảm giác rằng nó cố tình bỏ qua thực tế là hoán vị là một vấn đề tổ hợp. Đây là điều chúng ta đã biết ! Đây không phải là một lời chỉ trích, chỉ là một quan sát.
Như câu hỏi thực sự: làm thế nào để tiến về phía trước? Vâng, có một cách là cung cấp càng nhiều kiến thức về vấn đề chúng ta đang tuyên bố với máy tính.
Nỗ lực tốt nhất mà tôi biết để thực sự giải quyết vấn đề được trình bày trong các cuốn sách do Alexander Stepanov đồng tác giả, "Các yếu tố lập trình" và "Từ toán học đến lập trình chung" . Đáng buồn là tôi không theo kịp nhiệm vụ tóm tắt (hoặc thậm chí hiểu đầy đủ) mọi thứ trong những cuốn sách này. Tuy nhiên, cách tiếp cận là xác định các thuật toán thư viện và cấu trúc dữ liệu hiệu quả (hoặc thậm chí tối ưu), theo quy định rằng tất cả các thuộc tính có liên quan của đầu vào đều được biết trước. Kết quả cuối cùng là:
- Mỗi phép biến đổi được xác định rõ là một sự tinh chỉnh các ràng buộc đã có (các thuộc tính đã biết);
- Chúng tôi để máy tính quyết định chuyển đổi nào là tối ưu dựa trên các ràng buộc hiện có.
Về lý do tại sao nó chưa xảy ra, khoa học máy tính là một lĩnh vực rất trẻ và chúng tôi vẫn đang đối phó với việc thực sự đánh giá cao sự mới lạ của hầu hết nó.
PS
Để cung cấp cho bạn một ý nghĩa của những gì tôi muốn nói bằng cách "tinh chỉnh việc thực hiện": ví dụ như vấn đề dễ dàng để có được yếu tố cuối cùng của danh sách, trong Prolog. Các giải pháp khai báo chính tắc là để nói:
last(List, Last) :-
append(_, [Last], List).
Ở đây, ý nghĩa khai báo append/3
là:
List1AndList2
là sự kết hợp của List1
vàList2
Vì trong đối số thứ hai append/3
chúng ta có một danh sách chỉ có một phần tử và đối số thứ nhất bị bỏ qua (dấu gạch dưới), chúng ta có một phần của danh sách gốc loại bỏ phần trước của danh sách ( List1
trong ngữ cảnh append/3
) và yêu cầu mặt sau ( List2
trong bối cảnh append/3
) thực sự là một danh sách chỉ có một yếu tố: vì vậy, nó là yếu tố cuối cùng.
Các thực hiện thực tế được cung cấp bởi SWI-Prolog , tuy nhiên, nói:
last([X|Xs], Last) :-
last_(Xs, X, Last).
last_([], Last, Last).
last_([X|Xs], _, Last) :-
last_(Xs, X, Last).
Đây vẫn là tuyên bố độc đáo. Đọc từ trên xuống dưới, nó nói:
Phần tử cuối cùng của danh sách chỉ có ý nghĩa đối với danh sách ít nhất một phần tử. Phần tử cuối cùng cho một cặp đuôi và phần đầu của danh sách, sau đó là: phần đầu, khi phần đuôi trống hoặc phần cuối của phần đuôi không trống.
Lý do tại sao việc triển khai này được cung cấp là để giải quyết các vấn đề thực tế xung quanh mô hình thực thi của Prolog. Lý tưởng nhất, nó không nên tạo ra sự khác biệt mà việc thực hiện được sử dụng. Tương tự như vậy, chúng ta có thể nói:
last(List, Last) :-
reverse(List, [Last|_]).
Phần tử cuối cùng của danh sách là phần tử đầu tiên của danh sách đảo ngược.
Nếu bạn muốn nhận được đầy đủ các cuộc thảo luận không có hồi kết về những gì tốt, Prolog khai báo, chỉ cần xem qua một số câu hỏi và câu trả lời trong thẻ Prolog trên Stack Overflow .