TÊN BÀI:LẬP TRÌNH LOGIC VÀ LẬP TRÌNH HÀM

Một phần của tài liệu Giáo trình lý thuyết ngôn ngữ lập trình (nghề lập trình máy tính) (Trang 108 - 123)

D. Truyền tham số dạng biến toàn cục

5. Ngữ nghĩa biểu thị

TÊN BÀI:LẬP TRÌNH LOGIC VÀ LẬP TRÌNH HÀM

MÃ BÀI : ITPRG3-06.7

Giới thiệu

Trong các họ ngôn ngữ lập trình, lập trình hàm và lập trình logic là hai họ ngôn ngữ lập trình ít phổ dụng hơn, do nét đặc trưng trong ứng dụng của nó. Tuy nhiên, trong lĩnh vực công nghệ thông tin, để giải quyết một số bài toán thực tế liên quan đến hệ chuyên gia, cơ sở tri thức hay trí tuệ nhân tạo thì việc sử dụng các họ ngôn ngữ lập trình này là rất phù hợp và mang lại hiệu quả cao.

Mục tiêu thực hiện

- Hiểu rõ lập trình logic và sự khác biệt với lập trình mệnh lệnh - Các thành phần ngôn ngữ lập trình logic

- Xác định ngữ nghĩa của các ký hiệu

- Nắm được thành phần cơ bản của ngôn ngữ Prolog - Viết được các chương trình cỡ nhỏ và vừa bằng Prolog - Hiểu cơ chế lập trình hàm

- Thực hiện các phép biến đổi hàm và ngữ nghĩa hàm

Nội dung chính

Bài học này trình bày hai phương pháp: lập trình logic và lập trình hàm. Đồng thời cũng giới thiệu hai ngôn ngữ cụ thể là lập trình logic Prolog và lập trình hàm Scheme.

Giới thiệu lập trình logic

Lập trình logic được sử dụng phổ biến trong lĩnh vực trí tuệ nhân tạo và hệ chuyên gia. Các ngôn ngữ lập trình logic rất thích hợp để giải quyết các bài toán liên quan đến các đối tượng (object) và mối quan hệ (relation) giữa chúng.

Nguyên lý lập trình logic dựa trên các mệnh đề Horn (Horn clauses). Một mệnh đề Horn biểu diễn một sự kiện hay sự việc nào nào đó là đúng hoặc không đúng, xảy ra hoặc không xảy ra (có hoặc không có, …). Ví dụ sau đây là một số mệnh đề Horn:

1. Nếu một người An cha của Lộc Lộc là cha của Dương thì An là ông của Dương. 2. Hùng là cha của Tuyết.

3. Nếu một học viên chịu khó học hỏi thì học viên đó có kết quả tốt. 4. Học viên Linh có kết quả tốt.

5. Tất cả mọi người đều phải chết

(Hoặc Nếu ai đó là người, thì ai đó phải chết). 6. Socrat là người.

Trong các mệnh đề Horn ở trên, các mệnh đề 1, 3, 5 được gọi là các luật (rule), các mệnh đề còn lại được gọi là các sự kiện (fact). Một chưưng trình logic có thể được xem như là một cơ sở dữ liệu gồm các mệnh đề Horn, hoặc dạng luật hoặc dạng sự kiện, chảng hạn như tất cả các sự kiện và luật từ 1 đến 6 ở trên. Ngưới sử dụng gọi chạy một chương trình logic bằng cách đặt các câu hỏi (question) truy vấn trên cơ sở dũ liệu này, chẳng hạn câu hỏi:

Socrat có chết không (tương đương với khẳng định “Socrat chết” là đúng hay sai)?

Một hệ thống logic sẽ thực hiện chương trình theo cách suy luận – tìm kiếm dựa trên vốn tri thức (knowledge) đã có là chương trình – cơ sở dữ liệu, để chứng minh câu hỏi là một khẳng định đúng (Yes) hoặc sai (No). Với câu hỏi trên, hệ thống tìm kiếm trong cơ sở dữ liệu khẳng định Socrat chết bằng cách tìm xem ai đó là Socrat và tìm thấy luật 5 thỏa mãn (vế

thì). Vận dụng luật 5, hệ thống nhận được Socrat là người (vế nếu) chính là sự kiện 6. Từ đó câu trả lời sẽ là:

Yes

Có nghĩa là sự kiện Socrat chết là đúng.

Trong họ các ngôn ngữ logic thì Prolog (nghĩa là Programing in Logic) là ngôn ngữ lập trình được sử dụng rộng rãi nhất. Ngôn ngữ này ra đời năm 1970 do một nhóm nghiên cứu người Pháp đề xuất, sau đó Prolog nhanh chóng được áp dụng rộng rãi ở châu Âu và nhiều nước trên thế giới.

Từ năm 1980, Prolog được công nhận như là một ngôn ngữ phát triển trong lĩnh vực Trí tuệ Nhân tạo. Và gần đây, Prolog còn được ứng dụng trong lập trình bởi các ràng buộc.

Ngữ nghĩa các kí hiệu Một số khái niệm

Một chương trình Prolog là một cơ sở dữ liệu gồm các mệnh đề (clause). Mỗi mệnh đề được xây dựng từ các vị từ (predicat). Một vị từ là một phát biểu nào đó về các đối tượng có giá trị chân lý đúng (true) hoặc sai (false). Một vị từ có thể có các đối là các nguyên tử (logic atom).

Mỗi nguyên tử biểu diễn một quan hệ giữa các hạng (term). Như vậy, hạng và quan hệ giữa các hạng tạo thành mệnh đề. Hạng được xem là những đối tượng dữ liệu trong một chương trình Prolog. Hạng có thể là hạng sơ cấp (elementary term) gồm hằng (constant) hay trực kiện (literal), biến (variable) và các hạng phức hợp (compound term).

Các hạng phức hợp biểu diễn các đối tượng phức tạp của bài toán cần giải quyếtthuộc lĩnh vực đang xét. Hạng phức hợp là một hàm tử (functor) có chứa các đối (argument), có dạng:

Tên_hàm_tử (đối_1, đối_2, …)

Tên hàm tử là một chuỗi chữ cái hoặc chữ số được batư đầu bởi chữ cái. Các đối có thể là các biến, hạng sơ cấp, hoặc hạng phức hợp.

Ví dụ các hàm tử: a(x, y, 5).

Person(Hùng, 1980, địachỉ(15, Pasteur, ĐàNẵng)).

Sau mỗi mệnh đề, Prolog quy ước kết thúc bởi một dấu chấm.

Mệnh đề có thể là một sự kiện, một luật hay một câu hỏi. Mỗi mệnh đề được viết như sau: - Sự kiện: < … >.

- Luật: < … > :- < … >. - Câu hỏi: ?- < … >.

Các kiểu dữ liệu Prolog

Hình 7.1 sau biểu diễn phân lớp các kiểu dữ liệu trong Prolog.

Hình 7.1 Các kiểu dữ liệu trong Prolog

Như thế, các kiểu dữ liệu gồm các kiểu sơ cấp và các kiểu phức hợp. Các kiểu sơ cấp gồm các kiểu hằng và biến.

Kiểu hằng số gồm số nguyên và số thực. Cú pháp số nguyên và số thực rất đơn giản, ví dụ:

10 1111 546.67 -123.05

Các số thực rất ít được sử dụng trong Prolog, bởi vì Prolog là một ngôn ngữ lập trình kí hiệu, phi số.

Kiểu hằng logic có giá trị true và fail.

Kiểu hằng chuỗi kí tự được đặt giữa hai dấu nháy kép. Ví dụ: “Chuoi @ ki tu #” Chuỗi kí tự tùy ý

“” Chuỗi rỗng

Kiểu hằng nguyên tử là một chuỗi kí tự ở một trong 3 dạng sau:

kiểu dữ liệu

kiểu sơ cấp kiểu phức hợp

hằng biến

1. Chuỗi gồm chữ cái, chữ số và kí tự _ luôn được bắt đầu bởi một chữ cái in thường, ví dụ:

ok student

alain_smith x23

2. Chuỗi các kí hiệu đặc biệt: :::=== <---->

3. Chuỗi đặt giữa hai dấu nháy đơn được bắt đầu bởi kí tự in hoa: ‘Alain Smith’ ‘No thing’

Biến là một chuỗi kí tự gồm chữ cái, chữ số, được bắt đầu bởi chữ in hoa hoặc dấu _: X, Y, Z

Prog, Array_Of_Nat, _X, _A320

Kiểu dữ liệu phức hợp là các kiểu dữ liệu có cấu trúc (cây, danh sách, …), tương tự cấu trúc bản ghi, là đối tượng nhiều thành phần, mỗi thành phần lại có thể là một cấu trúc. Ví dụ:

triangle(point(1, 4), point(6, 4), point(5, 8))

Chú thích

Trong chương trình Prolog, chú thích (comment) được đặt giữa hai cặp ký hiệu /* và */. Ví dụ:

/******************************/

/** Đây là chú thích trong Prolog **/ /******************************/

Trong trường hợp muốn đặt một chú thích ngắn sau mỗi phần khai báo Prolog cho đến hết dòng thì có thể đặt trước kí hiệu %.

Thực nghiệm ngôn ngữ lập trình Prolog

Như chúng ta đã biết, một chương trình Prolog là một cơ sở dữ liệu gồm các mệnh đề. Mỗi mệnh đề hoặc là sự kiện hoặc là luật.

Xây dựng sự kiện

Một sự kiện khẳng định một thực thể có một vài tính chất nào đó. Để xây dựng các sự kiện chúng ta lấy ví dụ về cây gia hệ

Hình 7.2.Ví dụ về cây gia hệ anna jerrry luc toto sam dam jan

Trong cây gia hệ, các nút chỉ người, các mũ tên chỉ quan hệ cha (hay mẹ) của (parent of). Sự kiện anna là cha (hay mẹ) của luc được viết thành vị từ như sau:

parent(anna, luc).

Vị từ parent có hai đối là anna và luc.

Từ cây gia hệ trên chúng ta có thể viết các vị từ sau: parent(anna, luc). parent(jerry, luc). parent(jerry, dam). parent(luc, toto). parent(luc, sam). parent(toto, jan).

Sau khi hệ thống nhận được chương trình này mà thực ra là một cơ sở dữ liệu thì người sử dụng có thể đặt ra các câu hỏi liên quan đến quan hệ parent. Chẳng hạn, câu hỏi luc có phải là cha (hay mẹ) của toto không được chuyển thành câu hỏi trong hệ thống đối thoại của Prolog như sau (dấu nhắc ?-):

?- parent(luc, toto).

Sau khi tìm thấy sự kiện này, Prolog trả lời: Yes

Tiếp tục đặt câu hỏi khác: ?- parent(luc, jan). No

Bởi vì Prolog không tìm thấy sự kiện Luc là mẹ của Jan.

Hơn nữa, chúng ta có thể đặt ra các câu hỏi khác. Chẳng hạn, ai là cha (hay mẹ) của của luc?

?- parent(X, luc). anna ;

jerry Yes

Với câu hỏi này Prolog sẽ có hai câu trả lời. Để biết câu trả lời tiếp theo, trong hầu hết các trình cài đặt Prolog, người sử dụng phải gõ vào dấu chấm phẩy (;) như trong ví dụ trên. Lưu ý, nếu đã hết phương án trả lời mà vẫn tiếp tục yêu cầu (;) thì Prolog trả lời No ngược lại trả lời Yes.

Chúng ta có thể đặt các câu hỏi tổng quát hơn, như ai là cha (hay mẹ) của ai? Nói cách khác cần tìm tất cả X và Y sao cho X là cha (hay mẹ) của Y. Ta đặt câu hỏi như sau:

?- parent(X, Y).

Sau khi nhận được câu hỏi trên, Prolog sẽ lân lượt tìm tất cả các cặp cha (hay mẹ) - conm thỏa mãn yêu cầu:

X = anna Y = luc ; X = jerry

Y = luc ; X = jerry Y = dam ; X = luc Y = toto ; X = luc Y = sam ; X = toto Y = jan Yes

Tùy theo trình cài đặt Prolog chúng ta có thể gõ vào một dấu chấm (.) hoặc Enter để chấm dứt giữa chừng luồng trả lời.

Ngoài ra, chúng ta còn có thể đưa ra các câu hỏi phức tạp hơn, chẳng hạn ai là cháu của jerry. Thực tế, thì quan hệ cháu chưa được định nghĩa tuy nhiên chúng ta có thể tách câu hỏi thành hai thành phần sơ cấp:

Ai là con của jerry? Giả sử có tên là X. Ai là con của X? Giả sử có tên là Y.

Như vậy Y sẽ là cháu của jerry. Quan hệ cháu được thiết lập từ quan hệ cha (hay mẹ) (hay ngược lại là quan hệ con).

Câu hỏi có thể được đặt ra như sau: ?- parent(jerry, X), parent(X, Y). Prolog trả lời: X = luc Y = toto ; X = luc Y = sam Yes

Như thế, Prolog đưa ra hai trả lời là toto và sam.

Mỗi câu hỏi trong Prolog được gọi là một đích (goal) cần phải được thỏa mãn. Nếu câu trả lời là Yes thì đích được thỏa mãn hay thành công, ngược lại câu trả lời là No thì đích là không được thỏa mãn hay thất bại.

Xây dựng luật

Từ chương trình Prolog trên, chúng ta có thể thêm vào các sự kiện về giới tính như sau: man(luc). man(jerry). man(dam). man(sam). woman(anna). woman(toto). woman(jan).

Trong ví dụ trên chúng ta đã nói đến quan hệ con, nhưng thực ra chúng ta vẫn chưa định nghĩa chúng mà chỉ coi là hệ quả của quan hệ cha (hay mẹ). Thật vây, jerry là cha (hay mẹ) của luc thì đương nhiên luc là con của jerry. Chúng ta có thể đưa vào quan hệ mới child như sau:

child(luc, jerry).

Thay vì định nghĩa từng sự kiện như thế này cho quan hệ cha (hay mẹ) - con, ta định nghĩa luật như sau:

child(Y, X) :- parent(X, Y).

Luật trên được hiểu là: với mọi X và Y, Y là con của X, nếu X là cha (hay mẹ) của của Y. Như thế, một luật bao gồm hai phần:

- phần bên phải chỉ điều kiện, còn được gọi là thân (body) của luật; - phần bên trái chỉ kết luận, còn được gọi là đầu (head) của luật.

Có sự khác nhau giữa sự kiện và luật. Một sự kiện luôn đúng, khong chịu bất cứ ràng buộc nào cả. Trong khi một luật chỉ đúng khi một số thuộc tính nào đó phải được thỏa mãn. Thật vậy, nếu parent(X, Y) là đúng thì child(Y, X) cũng phải đúng là kết quả logic của phép suy luận.

Ngoài ra chúng ta có thể định nghĩa các luật (dấu phẩy (,) chỉ phép và logic (and)) để bổ sung thêm các quan hệ mới, như quan hệ mẹ/cha (mother/father) và quan hệ ông bà (grandparent).

mother(X, Y) :- parent(X, Y), woman(X). father(X, Y) :- parent(X, Y), man(X).

grandparent(X, Z) :- parent(X, Y), parent(Y, Z).

Chẳng hạn quan hệ mẹ được hiểu là: với mọi X và Y, nếu X là cha (hay mẹ) của Y và X là phụ nữ thì X là mẹ của Y.

Bây giờ chúng ta xem cách Prolog sử dụng các luật như thế nào. Giả sử ta đặt câu hỏi: luc có phải là con của anna không.

?- child(luc, anna).

Trong chương trình không có sự kiện nào liên quan đến con, như thế phải tìm các luật. Luật trên áp dụng đối với đối tưuợng tổng quát bất kỳ mà ta lại quan tâm đến các đối tượng cụ thể luc và anna. Prolog sẽ sử dụng phép thế (substitution) bằng cách gán giá trị luc cho biến Y và anna cho biến X. Ta nói rằng X và Y đã được ràng buộc (bound): X = anna và Y = luc

Lúc này phần điều kiện có giá trị parent(anna, luc) trở thành đích con (sub-goal) để Prolog thay thế cho đích child(luc, anna). Tuy nhiên, đích này chính là sự kiện đã có, nên câu trả lời sẽ là:

Yes

Ngoài ra, Prolog còn cung cấp chúng ta cách định nghĩa luật đệ quy (recursive rule). Bây giờ chúng ta muốn thêm quan hệ tổ tiên (ancestor). Ta phân biệt ra hai loại quan hệ: tổ tiên trực tiếp và tổ tiên gián tiếp.

Tổ tiên trực tiếp được định nghĩa như sau: với mọi X và Z, X là tổ tiên trực tiếp của Z nếu X là cha hay mẹ của Z.

ancestor(X, Z):- parent(X, Z).

Tổ tiên gián tiếp được định nghĩa phức tạp hơn, nếu càng mở rộng lớp hậu duệ. Chẳng hạn: ancestor(X, Z):- % tổ tiên là ông bà

parent(X, Y), parent(Y, Z).

ancestor(X, Z):- % tổ tiên là cố ông cố bà parent(X, Y1),

parent(Y1, Y2), parent(Y2, Z). …

Tuy nhiên nhờ phép đệ quy, tổ tiên gián tiếp được định nghĩa một cách đơn giản hơn như sau: với mọi X và Z, X là tổ tiên của Z nếu tồn tại Y sao cho X là cha hay mẹ của Y và Y là tổ tiên của Z. ancestor(X, Z):- parent(X, Z). ancestor(X, Z):- parent(X, Y), ancestor(Y, Z).

Nếu ta đặt câu hỏi: anna là tổ tiên của ai? ?- ancestor(anna, X). Câu trả lời sẽ là: X = luc ; X = toto ; X = sam ; X = jan Yes

Trong Prolog, hầu hết các chương trình phức tạp đều sử dụng đệ quy, đệ quy là một khả năng mạnh của Prolog.

Sử dụng biến trong Prolog

Các biến xuất hiện trong các mệnh đề gọi là biến tự do. Người ta giải thiết rằng các biến là được lượng tử toàn thể và được đọc là “với mọi”. Tuy nhiên, có nhiều cách giải thích khác nhau trong trường hợp các biến chỉ xuất hiện trong phần bên phải của luật. Ví dụ:

haveachild(X) :- parent(X, Y). có thể được đọc như sau:

(a) Với mọi X và Y, nếu X là cha (hay mẹ) của Y thì X có một người con.

(b) Với mọi X, X có một người con nếu tồn tại một Y sao cho X là cha (hay mẹ) của Y. Ngoài ra, khi một biến chỉ xuất hiện một lần trong các mệnh đề thì không cần đặt tên cho nó. Trong trường hợp này, Prolog cho phép sử dụng biến vô danh (anonymous variable) là các biến có tên chỉ là một dấu gạch dưới (_).

Quay trở lại với ví dụ trên, ta nhận thấy trong luật trên đích haveachild không phụ thuộc gì vào Y, nên ta có thể dùng biến vô danh như sau:

haveachild(X) :- parent(X, _).

Nghĩa là, X có một người con nếu X là cha (hay mẹ) của ai đó.

Phạm vi sử dụng (lexical scope) của một biến trong một mệnh đề không vượt ra khỏi mệnh đề đó. Có nghĩa là, nếu một biến X xuất hiện trong hai mệnh đề khác nhau, thì sẽ tương ứng với hai biến phân biệt nhau.

Logic trong Prolog

Prolog có cú pháp là những công thức logic vị từ bậc một (first order predicate logic), được viết dưới dạng các mệnh đề (các lượng tử  và  xuất hiện không tường minh), nhưng hạn chế chỉ đơn thuần là các mênh đề Horn, là các mệnh đề được khẳng định là đúng.

Prolog diễn giải chương trình là theo kiểu toán học: các sự kiện và các luật là các tiên đề, câu hỏi của người sử dụng như là một định lý cần khẳng định. Prolog sẽ tìm cách chứng minh định lý này, nghĩa là chỉ ra rằng định lý có thể được suy diễn một cách logic từ các tiên đề.

Về mặt thủ tục, Prolog sử dụng phương pháp suy diễn quay lui (back-tracking) để hợp giải (resolution) bài toán, được gọi là chiến lược hợp giải. Có thể tóm tắt như sau:

Để chứng minh P(a), người ta tìm sự kiện P(t) hoặc một luật P(t) :- L1, L2, …, Ln sao cho a có thể hợp nhất được (unifiable) với t nhờ so khớp. Nếu tìm được sự kiện P(a) như vậy việc chứng minh là kết thúc. Còn nếu tìm được P(t) là luật, cần lần lượt chứng minh vế phải của

Một phần của tài liệu Giáo trình lý thuyết ngôn ngữ lập trình (nghề lập trình máy tính) (Trang 108 - 123)

Tải bản đầy đủ (PDF)

(145 trang)