PHẦN I. PROLOG _______________________________________________________ 1
Chương I. Vị từ (predicate) - Tư duy lập trình và định nghĩa vấn đề trên Prolog ______ 2
Chương II. Các clause, cách giải thích các vấn đề trên Prolog _______________________ 5
Chương III. Môi trường lập trình B-Prolog _______________________________________ 7
III.1 Giới thiệu sơ nét về B-Prolog ____________________________________________________ 7
III.2 Cài đặt và làm việc với B-Prolog__________________________________________________ 7
III.3 Gỡ rối chương trình (debugging)__________________________________________________ 8
III.4 Các thuật ngữ cơ bản trong B-Prolog ______________________________________________ 9
III.5 Các kiểu dữ liệu và các vị từ xây dựng sẵn (built-in) cơ bản trong B-Prolog _______________ 10
Chương IV. Thực thi chương trình. - Đặt câu hỏi và nhận câu trả lời_________________ 12
Chương V. IV. Phép hợp nhất - Cơ chế tìm câu trả lời của Prolog. __________________ 15
V.1 Phép hợp nhất _______________________________________________________________ 15
V.2 Cơ chế tìm câu trả lời của Prolog ________________________________________________ 16
Chương VI. Sự quay lui - Khống chế số lượng lời giải -Vị từ nhát cắt và fail___________ 19
VI.1 Sự quay lui (back-tracing) trên Prolog_____________________________________________ 19
VI.2 Khống chế số lượng lời giải_____________________________________________________ 20
Chương VII. Lập trình đệ quy với Prolog ______________________________________ 22
Chương VIII. Danh sách trên Prolog ___________________________________________ 24
VIII.1 Cấu trúc của danh sách _____________________________________________________ 24
Chương IX. Lập trình đệ quy với danh sách trên Prolog ___________________________ 26
Chương X. Danh sách hai chiều _______________________________________________ 29
PHẦN II. LISP ________________________________________________________ 30
Chương I. Giới thiệu ________________________________________________________ 31
I.1 Lịch sử phát triển _______________________________________________________________ 31
I.2 Đặc điểm của gcLisp ____________________________________________________________ 31
1. Các đặc điểm của ngôn ngữ_____________________________________________________ 31
2. Kiểu dữ liệu _________________________________________________________________ 32
Chương II. Lập trình với gcLisp _______________________________________________ 33
II.1 Các khái niệm cơ bản _________________________________________________________ 33
1. Bắt đầu với LISP _____________________________________________________________ 33
2. Hàm và dữ liệu trong LISP _____________________________________________________ 34
3. Đánh giá____________________________________________________________________ 34
II.2 Các hàm xử lý trên danh sách ___________________________________________________ 34
1. FIRST và REST – CAR và CDR_________________________________________________ 34
2. CONS, APPEND, LIST________________________________________________________ 35ii
3. NTHCDR, BUTLAST và LAST _________________________________________________ 36
4. LENGTH và REVERSE _______________________________________________________ 37
II.3 Thao tác trên Integer, Ratio, Floating-Point Numbers, . ______________________________ 37
II.4 Lập trình hướng dữ liệu________________________________________________________ 38
1. ASSOC ____________________________________________________________________ 38
2. ACONS ____________________________________________________________________ 38
Chương III. Hàm và Biến cục bộ _______________________________________________ 40
III.1 Định nghĩa hàm – Chương trình đệ quy trong Lisp___________________________________ 40
III.2 Biến cục bộ _________________________________________________________________ 41
1. LET _______________________________________________________________________ 41
2. LET* ______________________________________________________________________ 42
Chương IV. Các vị từ và biểu thức điều kiện _____________________________________ 43
IV.1 Vị từ_______________________________________________________________________ 43
IV.2 Các phép so sánh: EQUAL, EQ, EQL và =_________________________________________ 43
IV.3 Vị từ MEMBER______________________________________________________________ 44
IV.4 Vị từ NULL và ENDP _________________________________________________________ 45
IV.5 Các vị từ xác định kiểu dữ liệu __________________________________________________ 45
IV.6 Các vị từ trên số______________________________________________________________ 47
IV.7 Các toán tử logic _____________________________________________________________ 48
1. AND ______________________________________________________________________ 48
2. OR ________________________________________________________________________ 49
3. NOT_______________________________________________________________________ 49
IV.8 Các dạng điều kiện ___________________________________________________________ 50
1. IF, WHEN và UNLESS________________________________________________________ 50
2. COND _____________________________________________________________________ 51
3. CASE______________________________________________________________________ 51
Chương V. Trừu tượng hóa dữ liệu ____________________________________________ 53
V.1 Các trường của một ký hiệu_____________________________________________________ 53
V.2 Doublets____________________________________________________________________ 53
1. Doublets____________________________________________________________________ 53
2. Pointed pair _________________________________________________________________ 54
3. Ký hiệu pointed pair __________________________________________________________ 54
4. Doublets trong LISP __________________________________________________________ 54
V.3 Lời gọi hàm tính toán _________________________________________________________ 55
1. Apply______________________________________________________________________ 55
2. Funcall _____________________________________________________________________ 55
V.4 Hàm vô danh ________________________________________________________________ 56
1. Lambda expression ___________________________________________________________ 56
2. Hàm vô danh và biến cục bộ ____________________________________________________ 56
Chương VI. Lặp trên số và trên danh sách _______________________________________ 57
VI.1 Các cấu trúc lặp ______________________________________________________________ 57
1. DOTIMES __________________________________________________________________ 57iii
2. DOLIST Hỗ trợ lặp trên danh sách _______________________________________________ 57
3. DO tổng quát hơn DOLIST và DOTIMES _________________________________________ 59
VI.2 Các dạng đặc biệt_____________________________________________________________ 59
1. progn ______________________________________________________________________ 59
2. prog1 ______________________________________________________________________ 59
Chương VII. Các thao tác với tập tin __________________________________________ 61
VII.1 Lưu lại tập tin chương trình và dữ liệu ____________________________________________ 61
VII.2 Biên dịch tập tin______________________________________________________________ 61
VII.3 Debugging __________________________________________________________________ 61
Chương VIII. Cài đặt và sử dụng gcLisp ________________________________________ 63
VIII.1 Cài đặt___________________________________________________________________ 63
VIII.2 Startup Gclisp _____________________________________________________________ 63
VIII.3 Phím nóng________________________________________________________________ 63
VIII.4 Dòng lệnh ________________________________________________________________ 64
VIII.5 Lệnh tiền tố (Prefix command) ________________________________________________ 64
VIII.6 Cửa sổ soạn thảo GMAC ____________________________________________________ 64
VIII.7 Load file vào gclisp_________________________________________________________ 64
PHẦN III. SMALLTALK_________________________________________________ 66
Chương I. LÝ THUYẾT VỀ OOP VÀ NGÔN NGỮ SMALLTALK ________________ 67
I.1 Lập trình hướng đối tượng (Object Oriented Programming) với Smalltalk ___________________ 67
1. Đối tượng (Object) - Các thành phần (member) của đối tượng: Các thuộc tính (properties) và các
phương thức (methods) - Sự bao đóng (encapsulation). ____________________________________ 67
2. Khái niệm class - Mối quan hệ giữa object và class - Khái niệm instance. _________________ 67
3. Phương thức - Thông điệp (message) - Đối tượng nhận thông điệp (receiver). Đối số của thông
điệp (argument) ___________________________________________________________________ 68
4. Các loại thông điệp: unary, binary và keyword. Độ ưu tiên giữa các thông điệp. ____________ 69
5. Câu lệnh (statement) - kịch bản (script)____________________________________________ 70
6. Che giấu thông tin (hiding information) ___________________________________________ 71
7. Sự thừa kế (inheritance) - Che phủ (override) - Sự dẩn xuất (derivation) - Mối quan hệ giữa các
đối tượng: cây các lớp. _____________________________________________________________ 71
8. Tính đa hình (polymorphism) - Sự ràng buộc muộn (late - binding)______________________ 72
I.2 Ngôn ngữ Smalltalk _____________________________________________________________ 72
1. Object trên Smalltalk. Thuộc tính thường và thuộc tính indexed. Các thành phần cho đối tượng và
thành phần cho lớp ________________________________________________________________ 72
2. Các literal - object: Integer, Float, Character, Boolean, Array, String, Context _____________ 73
3. Khai báo biến - Ràng buộc về kiểu trên ngôn ngữ Smalltalk - Phát biểu gán - Phát biểu trả về _ 74
4. Định nghĩa một object mới. Các phương thức new và new: ____________________________ 75
5. Định nghĩa một class mới và phương thức mới - Sự biên dịch offline và online một phương thức
76
6. Bên trong phương thức - Các từ khóa self và super___________________________________ 76
7. Các phương thức primitive _____________________________________________________ 78
8. Khái niệm về MetaClass - Sử dụng MetaClass - Lập trình OOP động (dynamic) với Smalltalk 78iv
9. Các lớp đặc biệt: Compiler, Window, ViewManager, Prompter. _______________________ 79
I.3 Một số kỹ thuật lập trình căn bản trên Smalltalk _______________________________________ 79
1. Sự mô phỏng các cấu trúc điều khiển. _____________________________________________ 79
2. Thao tác trên tập hợp (collection). Một số kỹ thuật xử lý trên tập hợp.____________________ 80
Chương II. HƯỚNG DẪN SỬ DỤNG VWIN VERSION 2.0 _______________________ 83
II.1 Hướng dẫn sử dụng chương trình VWIN: __________________________________________ 83
1. Thao tác trên hệ thống lớp ______________________________________________________ 83
2. Lập trình ___________________________________________________________________ 85
3. Load và Save file. ____________________________________________________________ 88
4. Gỡ rối______________________________________________________________________ 88
II.2 Giới thiệu về một số lớp có sẳn của VWIN_________________________________________ 90
1. Lớp Object__________________________________________________________________ 90
2. Lớp Magnitude ______________________________________________________________ 91
3. Lớp Number, Integer, Float, Character ____________________________________________ 91
4. Lớp IndexedCollection: ________________________________________________________ 91
5. Lớp Context: ________________________________________________________________ 94
Chương III. MỘT SỐ KỸ THUẬT LẬP TRÌNH CĂN BẢN VỚI LỚP COLLECTION
TRÊN SMALLTALK - VÍ DỤ VÀ BÀI TẬP _______________________________________ 96
III.1 Sử dụng phương thức do: ______________________________________________________ 96
1. Ví dụ:______________________________________________________________________ 96
2. Bài tập đề nghị: ______________________________________________________________ 96
III.2 Sử dụng phương thức select: hoặc reject: __________________________________________ 97
1. Ví dụ:______________________________________________________________________ 97
2. Bài tập đề nghị_______________________________________________________________ 97
III.3 Sử dụng phương thức collect: ___________________________________________________ 97
1. Ví dụ:______________________________________________________________________ 97
2. Bài tập đề nghị: ______________________________________________________________ 98
III.4 Bài tập tổng hợp: _____________________________________________________________ 98
1. Ví dụ:______________________________________________________________________ 98
2. Bài tập đề nghị_______________________________________________________________ 98
rác (garbage
collection), gọi hàm đệ quy, theo vết và dò lỗi (tracing and debugging) và trình soạn thảo theo
cú pháp.
I.2 Đặc điểm của gcLisp
1. Các đặc điểm của ngôn ngữ
Từ khi được John McCarthy (MIT) nghĩ ra năm 1958, LISP được tinh chế dần đến version 1.5
và được sử sụng lâu dài về sau
Lisp là ngôn ngữ hướng chức năng (functional language hay applicative), dùng lối ký hiệu
tiền tố (prefix) và dấu ngoặc đơn:
f(x,y, z) được ký hiệu là (f x y z)
Tương tự x+y ký hiệu là (+ x y)
Bt. ⎟⎠
⎞⎜⎝
⎛ +
2
3sin πx viết trong Lisp như thế nào ? ( )( )( )( )2 / 3 * sin pix+
Lisp là ngôn ngữ thông dịch (interpreted language) (xem Error! Reference source not
found.)
Ví dụ:
Ngôn ngữ biên dịch
câu lệnh (instructions)
biên dịch
chương trình thực thi
thực thi
kết quả
Ngôn ngữ thông dịch
Biểu thức
đánh giá
trả lời
Kết quả
vòng lặp top-level
Hướng dẫn sử dụng Lisp
32
* (+ 3 4)
7
* (+ (* 3 4)(- 5 2))
15
*4
4
2. Kiểu dữ liệu
Lisp thao tác trên các loại dữ liệu:
¾ Biểu thức expression::= atom | list
¾ Danh sách list::= (expression1...expressionn)
Danh sách là một chuỗi các biểu thức ngăn cách nhau bởi khoảng trắng, tất cả đặt
trong dấu ngoặc đơn
¾ Atoms
atom::= số | chuỗi ký tự | ký hiệu
¾ Ký hiệu (~ identifier): từ tạo bởi các ký tự bất kỳ, ngoại trừ ( ) ‘ ` “ ; và khoảng
trắng
¾ Boolean: Lisp không có kiểu boolean. Trong Lisp, nil mang giá trị logic sai và tất cả
các biểu thức khác có giá trị đúng. Ký hiệu t dùng để chỉ trị logic đúng theo mặc định.
Các kiểu dữ liệu được xếp theo cấp bậc như sau:
Biến trong Lisp không có kiểu dữ liệu , cùng một biến có thể có nhiều kiểu dữ liệu khác
nhau.
Ví dụ:
* (setq a ‘(1 2 3))
(1 2 3)
* a
(1 2 3)
* (setq a 2)
2
* a
2
expression
list atom
symbol
real nillist
... number
... ... interger
Hướng dẫn sử dụng Lisp
33
Chương II. Lập trình với gcLisp
Bây giờ chúng ta sẽ cùng bắt đầu với LISP. Chương này sẽ giới thiệu một số hàm cơ bản
nhằm thao tác trên ký hiệu trong LISP. Đầu tiên là các hàm về số, sau đó là các hàm trên danh
sách. Phần này cũng sẽ giới thiệu một số thuật ngữ khi làm việc với LISP, chẳng hạn như dấu
nhắc, chú thích, hàm, đối số, chương trình, giải thuật, atom, số, ký hiệu, danh sách, các phần
tử trong danh sách, biểu thức, kiểu dữ liệu, dạng, đánh giá.
II.1 Các khái niệm cơ bản
1. Bắt đầu với LISP
Đối với phần lớn ngôn ngữ lập trình, cách tốt nhất để học là bắt đầu với một chương trình đơn
giản. Trong phần này, chúng ta sẽ làm quen với một số khái niệm cơ bản trong LISP.
Để bắt đầu, chúng ta tưởng tượng ta đang ngồi trước màn hình vi tính. Khi LISP không
thực hiện một thao tác gì, nó ở trạng thái tĩnh. Khi đó, LISP hiển thị dấu nhắc cho biết nó
đang đợi chúng ta gõ lệnh vào. Trong phần lớn các hệ COMMON LISP hiện nay, dấu nhắc
chương trình là dấu sao.
*
Những từ đi sau dấu chấm phẩy là ghi chú chèn vào chương trình. Tất cả những từ còn lại
sau dấu ; trong cùng dòng sẽ không được LISP xử lý.
Chúng ta khởi đầu với một ví dụ đơn giản minh họa khả năng của LISP về xử lý số học
* (+ 3.14 2.71)
5.85
Giả sử chúng ta muốn lưu trữ một số thông tin cần thiết của một người. Ví dụ, chúng ta
muốn ghi nhớ danh sách bạn bè, kẻ thù, của một người nào đó. Chúng ta dùng SETF (viết
tắt của set field).
* (setf friends ‘(dick jane sally))
(DICK JANE SALLY)
* friends
(DICK JANE SALLY)
* (setf enemies ‘(troll grinch ghost))
(TROLL GRINCH GHOST)
* enemies
(TROLL GRINCH GHOST)
Hai danh sách trên là những thông tin động và có thể thay đổi. Ví dụ như ghost không còn
là kẻ thù mà trở thành bạn bè. Khi đó cần cập nhật lại hai danh sách.
* (setf enemies (remove ‘ghost enemies))
(TROLL GRINCH)
* (setf friends (cons ‘ghost friends))
(GHOST DICK JANE SALLY)
* enemies
Hướng dẫn sử dụng Lisp
34
(TROLL GRINCH)
* friends
(GHOST DICK JANE SALLY)
Bây giờ chúng ta sẽ xem làm thế nào định nghĩa một hàm làm công việc tương tự. Ta định
nghĩa hàm tên NEWFRIEND thực hiện công việc đổi một người từ kẻ thù thành bạn bè.
(defun newfriend (name)
(setf enemies (remove name enemies))
(setf friends (cons name friends))
)
Với hàm NEWFRIEND, việc đổi tình trạng của GHOST từ kẻ thù thành bạn bè có thể
thực hiện bằng một dòng lệnh đơn giản.
* (newfriend ‘ghost)
2. Hàm và dữ liệu trong LISP
Lisp là ngôn ngữ đặc trưng cho việc xử lý danh sách
Chương trình được biểu diễn bằng các danh sách và có thể thao tác trên đó như dữ liệu
(+ (* 3 4) (- 5 2))
chương trình: hàm + áp dụng vào hai đối số
dữ liệu: danh sách gồm ba thành phần
Ở top-level, khi đóng vai trò là đối số của một hàm, một danh sách luôn được xem như là
sự áp dụng một hàm.
3. Đánh giá
‘Exp là cách viết tắt của (quote Exp)
*‘a
A
*‘‘a
(QUOTE A)
QUOTE không đánh giá đối số
Ngược lại với quote là hàm eval đánh giá giá trị của đối số
* (setq l ‘(a b c))
(A B C)
* (eval (list ‘car ‘l))
A
* (eval (list ‘* (1+ 3) 2))
6
Giá trị của (eval ‘Exp) là Exp
II.2 Các hàm xử lý trên danh sách
1. FIRST và REST – CAR và CDR
¾ FIRST trả về phần tử đầu tiên của danh sách
Hướng dẫn sử dụng Lisp
35
¾ REST trả về danh sách theo sau phần tử đầu tiên
Cho đến gần đây, phần lớn lập trình viên LISP vẫn dùng CAR và CDR thay cho FIRST
và REST. Ngoài chức năng tương tự, CAR và CDR có thể kết hợp với nhau.thành dạng
phức hợp CxxR, CxxxR hay CxxxxR. Mỗi x tượng trưng cho A – CAR hay D – CDR.
Quy ước:
* (car nil)
NIL
* (cdr nil)
NIL
Bài tập:
1. Lấy phần tử thứ ba của danh sách
(car (cdr (cdr l)))
có thể viết:
(caddr l)
CAR và CDR có thể kết hợp với nhau đến mức độ 4
Ví dụ:
(caadr l) = (car (car (cdr l)))
(cadar l) = (car (cdr (car l)))
2. Làm thế nào trích ra chuỗi example trong danh sách:
L=((this) is (an (example)) more complex)
L=((this) is (an (example)) more complex)
(cdr l) = (is (an (example)) more complex)
(cdr (cdr l)) = ((an (example)) more complex)
(car (cdr (cdr l))) = (an (example))
(cdr (car (cdr (cdr l)))) = ((example))
(car (cdr (car (cdr (cdr l))))) = (example)
(car (car (cdr (car (cdr (cdr l)))))) = example
2. CONS, APPEND, LIST
¾ LIST trả về danh sách các đối số
* (list ‘a (+ 3 1) ‘c)
(a 4 c)
* (list ‘(a b) ‘(c d))
((a b) (c d))
* (list ‘a nil)
(a nil)
¾ CONS thêm một phần tử vào đầu danh sách
(cons ‘a ‘(2 3))
(a 2 3)
(cons `(a b) ‘(c d))
((ab) c d)
(list `a nil)
Hướng dẫn sử dụng Lisp
36
(a)
(CAR (CONS a l)) = a
(CDR (CONS a l)) = l
Bt. Cho biết giá trị của các biểu thức sau:
1. (cons ‘a (cons ‘b (cons ‘c nil)))
(cons ‘a (cons ‘b (cons ‘c nil)))
= (cons ‘a (cons ‘b ‘(c)))
= (cons ‘a ‘(b c))
= (a b c)
2. (list (car ‘(car ((1) (2)))) (cdr (cdr ‘((1) (2)))))
(list (car ‘(car ((1) (2))))
(cdr (cdr ‘((1) (2)))))
= (list ‘car
(cdr (cdr ‘((1) (2)))))
= (list ‘car
(cdr ((2))))
= (list ‘car nil)
= (list ‘car nil)
= (car nil)
¾ APPEND kết hợp các phần tử của mọi danh sách đã cho
(setq l1 ‘(a b)
l2 ‘(x y))
= (x y)
(append l1 l2)
= (a b x y)
(append l1 ‘() l2 ‘())
= (a b x y)
3. NTHCDR, BUTLAST và LAST
¾ NTHCDR cắt n phần tử đầu danh sách, với thông số đầu chỉ số phần tử cần cắt
* (setq l ‘(a b c d e))
(a b c d e)
* (nthcdr 2 l)
(c d e)
¾ BUTLAST cắt n phần tử cuối danh sách, với thông số đầu là danh sách, thông số
thứ hai chỉ số phần tử cần cắt
* (setq l ‘(a b c d e))
(a b c d e)
* (butlast l 2)
(a b c)
* (butlast l 10)
NIL
¾ LAST trả về danh sách tất cả phần tử trừ phần tử cuối cùng đã bị loại ra
* (setq l ‘(a b c d e)
l1 ‘((a b) (c d)))
Hướng dẫn sử dụng Lisp
37
((a b) (c d))
* (last l)
(e)
* (last l1)
((c d))
4. LENGTH và REVERSE
¾ LENGTH trả về chiều dài của chuỗi
* (setq l ‘(a b c d e))
(a b c d e)
* (length l)
5
¾ REVERSE trả về chuỗi nghịch đảo
* (setq l ‘(a b c d e))
(a b c d e)
* (reverse l)
(e d c b a)
II.3 Thao tác trên Integer, Ratio, Floating-Point Numbers, ...
* (/ 1.234321 1.111)
1.111
* (/ 27 9)
3
* (\\ 10 4)
2
Tuy nhiên với trường hợp chia không chẵn, kết quả là một phân số:
* (/ 22 7)
22/7
Dùng FLOAT nếu muốn kết quả trả về là số thực có dấu phẩy động:
(float (/ 22 7))
3.14286
Dùng ROUND để làm tròn kết quả:
* (round (/ 22 7))
3 ;Thương – số nguyên gần nhất
1/7 ;Phần dư
* (+ round (/ 22 7)) (round (7/3)))
5
* (round (/ 5 2))
2
Một số hàm tính toán học:
* (MAX 2 4 3)
4
* (MIN 2 4 3)
2
Hướng dẫn sử dụng Lisp
38
* (expt 2 3)
8
* (expt 3 2)
9
* (expt 3.3 2.2)
13.827085
* (sqrt 9)
3
* (abs -5)
5
II.4 Lập trình hướng dữ liệu
1. ASSOC
¾ ASSOC gắn với một danh sách – association list hay còn gọi a-list
(setf sarah ‘((height .54) (weight 4.4)))
height và weight là khóa trong danh sách được gán cho SARAH; .54 và 4.4 là các
giá trị biểu thị bằng met và kilograms.
¾ Có thể lấy các thành phần từ một danh sách dùng ASSOC với các đối số là một khóa
và danh sách liên kết:
Ví dụ:
* (setf Andrew ‘((height .74) (weight 6.4)))
((HEIGHT 0.74) (WEIGHT 6.4))
* (assoc ‘height Andrew)
(HEIGHT 0.74)
Lưu ý ASSOC luôn trả về toàn bộ danh sách con tương ứng với khóa. Trong trường
hợp có nhiều danh sách con cùng khóa, danh sách đầu tiên sẽ được trả về.
2. ACONS
¾ ACONS để thêm một thành phần mới vào danh sách
Ví dụ:
* (acons ‘nick ‘Bobby Andrew)
((NICK . BOBBY) (HEIGHT 0.74) (WEIGHT 6.4))
Key Key
Value Value
(ASSOC )
(ACONS )
Hướng dẫn sử dụng Lisp
39
¾ Ví dụ: Chúng ta muốn dùng cùng một hàm thực hiện việc cộng hai số và nối hai chuỗi
¾ Giải pháp 1
(defun add(x y)
(cond
((numberp x) (+ x y))
((listp x) (append x y) )
)
)
¾ Giải pháp 2
* (setf add ‘((FIXNUM +) (CONS append)))
((FIXNUM +) (CONS APPEND))
* (defun add(x y)
(funcall (cadr (assoc (type-of x) add))
x y) )
funcall cho phép gọi các hàm tính toán
Hướng dẫn sử dụng Lisp
40
Chương III. Hàm và Biến cục bộ
Trong chương trước, chúng ta đã tìm hiểu về các hàm cơ bản cũng như một số thuật ngữ trên
LISP. Chương này sẽ giúp chúng ta trong việc viết các chương trình riêng cho mình và định
nghĩa một hàm mới dựa trên các hàm cơ bản. Các thuật ngữ mới cũng được thêm vào như
biến, tham số, thân chương trình.
III.1 Định nghĩa hàm – Chương trình đệ quy trong Lisp
Trong Lisp, chúng ta định nghĩa hàm bằng từ khóa defun:
(defun )
ký hiệu
đại diện cho các biến
biểu thức
Ví dụ:
* (defun square (x) (* x x))
SQUARE
* (square 3)
9
* (defun abs(x)
(if (>= x 0) x
(* -1 x) ) )
ABS
Vòng lặp trong Lisp được thực hiện chủ yếu nhờ vào đệ quy
Ví dụ: Tính giai thừa
Trong Pascal, hàm n! được viết bằng vòng lặp:
function fac(integer:n):integer;
var i:integer
begin
fac:=1;
for i:=1 to n do
fac:=fac*i;
end
Định nghĩa đệ quy của giai thừa:
n!=1*2*...*n
0!=1
n! = *n 1*2*...*n-1
(n-1)!
n!=n* (n-1) nếu n≥2
0!=1
Hướng dẫn sử dụng Lisp
41
Trong Lisp:
(defun fac(n)
(if (= n 0)
1
(* n fac (1- n)) ) )
Bài tập:
3. Viết hàm in ra phần tử thứ n trong danh sách
(defun nth (n l)
(if (= n 1)
(car l)
(nth (1- n) (cdr l)) ) )
4. Ví dụ đệ quy chéo hay lời gọi đệ quy:
(defun pair (n)
(or (= n 0)
(impair (1- n)) ) )
(defun impair (n)
(and ( n 0)
(pair (1- n)) ) )
III.2 Biến cục bộ
1. LET
(let ((var1 exp1) (varm expm)) expm+1 expn)
Chúng ta gán cho mỗi biến giá trị của biểu thức tương ứng, sau đó ta đánh giá
(progn expm+1 expn)
Ví dụ:
* (let ((x (fac 4))) (* x x))
= 576
¾ Các biến cục bộ che phủ các biến toàn cục
* (setq x 5)
5
* (let ((x 1)) x)
= 1
* x
5
* (let ((x 1)) (setq x 2) x)
2
*x
5
¾ Các biến cục bộ che phủ các đối số của một hàm
* (defun foo(x)
(let ((x 1)) x ) )
FOO
* (foo 4)
Hướng dẫn sử dụng Lisp
42
1
¾ Các liên kết được thực hiện song song
* (defun bar(x)
(let ((x 1) (y (1+ x)))
y) )
BAR
* (bar 4)
5
2. LET*
¾ Dạng let* thực hiện một liên kết tuần tự các đối số
* (defun bar(x)
(let ((x 1) (y (1- x)))
y) )
BAR
* (defun bar1(x)
(let* ((x 1) (y (1- x)))
y) )
BAR
* (bar 3)
2
* (bar1 3)
0
¾ let* tương ứng với let lồng nhau
* (defun bar(x)
(let ((x 1))
(let ((y (1+ x)))
y) ) )
BAR
Hướng dẫn sử dụng Lisp
43
Chương IV. Các vị từ và biểu thức điều kiện
Mục đích chính của chương này là giải thích một số từ mà chúng ta gọi là vị từ. Những từ
này, khi kết hợp với các biểu thức điều kiện sẽ cho phép chúng ta biến đổi những gì xảy ra
trong một số tình huống, và nhờ vào đó, chúng ta có thể định ra các hàm phức tạp hơn. Một số
vị từ còn cho phép thay đổi cách xử lý của các đối số.
IV.1 Vị từ
Một vị từ là một hàm trả về giá trị true hay false. FALSE trong LISP luôn được biểu diễn
bằng NIL. True được biểu diễn bằng ký hiệu T, bất kỳ cái gì khác NIL đều được xem là có giá
trị true.
Lưu ý là ở đây, T và NIL là các ký hiệu đặc biệt trong đó giá trị của chúng đã được thiết
lập sẵn cho T và NIL. Điều đó có nghĩa là giá trị của T là T và giá trị của NIL là NIL.
Ví dụ:
* t
T ; Giá trị của T là T
* nil
NIL ; Giá trị của NIL là NIL
IV.2 Các phép so sánh: EQUAL, EQ, EQL và =
EQUAL kiểm tra xem hai đối số và xem giá trị của chúng có cùng một biểu thức biểu diễn
hay không. EQUAL dùng cho cả atom và danh sách.
* (equal (+ 2 2) 4)
T
* (equal (+ 2 3) 3)
NIL
* (equal ‘(this is a list) (setf l ‘(this is a list)))
T
* (equal ‘(this is a list) l)
T
* (equal ‘(this is a list) (setf reverse-of-l ‘(list a is this)))
NIL
* (equal l (reverse reverse-of-l))
T
Các vị từ EQUAL, EQ, EQL và = đều có hai đối số và đều dùng để so sánh hai đối số.
Nếu hai đối số là bằng nhau, vị từ sẽ trả về giá trị T; ngược lại vị từ trả về giá trị NIL. Tuy
nhiên chúng có một số khác biệt trong cách thức so sánh. Nếu như EQUAL được xem là vị từ
chung dùng để so sánh, thì EQ và = được sử dụng với những mục đích khác hơn:
Vị từ Mục đích
equal Giá trị của hai đối số có cùng là một biểu thức hay không ?
Hướng dẫn sử dụng Lisp
44
eql
eq
=
Giá trị của hai đối số có cùng là một ký hiệu hay một số hay không ?
Giá trị của hai đối số có cùng là một ký hiệu hay không ?
Giá trị của hai đối số có cùng là một số hay không ?
Bây giờ, chúng ta xem xét một cách chi tiết hơn về liên hệ giữa các vị từ:
¾ EQUAL đầu tiên kiểm tra xem hai đối số có thỏa mãn EQL. Nếu không thỏa, nó sẽ
xem chúng là các danh sách và kiểm tra từng phần tử trong nó có thỏa EQUAL không.
¾ EQL đầu tiên kiểm tra hai đối số có thỏa mãn EQ. Nếu không thỏa, nó sẽ xem chúng
có cùng là số với cùng kiểu và giá trị.
¾ EQ kiểm tra hai đối số được lưu trữ cùng vùng nhớ trong máy tính hay không.
¾ = kiểm tra hai đối số biểu diễn cùng một số, ngay cả khi chúng không cùng kiểu.
Lưu ý rằng các số được so sánh phải là cùng kiểu để thỏa mãn EQL. Nhưng với = thì có
thể không cùng kiểu.
* (eql 4 4.0) ; Các số khác kiểu
NIL
* (eql 4 4) ; Các số có cùng kiểu
T
Các số không cùng kiểu thỏa =:
* (= 4 4.0)
T
Các đối số cho = phải có kiểu số. Bất kỳ một kiểu nào khác sẽ dẫn đến sinh ra lỗi.
IV.3 Vị từ MEMBER
Vị từ MEMBER kiểm tra xem đối số thứ nhất có phải là một phần tử của đối số thứ hai hay
không. Lưu ý là MEMBER trả về những gì còn lại trong danh sách khi tìm thấy phần tử trùng
hợp với đối số thứ nhất.
* (setf sentence ‘(tell me more about your mother please))
(TELL ME MORE ABOUT YOUR MOTHER PLEASE)
* (member ‘mother sentence)
(MOTHER PLEASE)
Một lưu ý nữa là MEMBER thông thường trả về NIL trừ khi đối số thứ nhất là một phần
tử trực tiếp của đối số thứ hai; còn nếu chỉ là phần tử nằm trong một danh sách thì không đủ.
* (setf pairs ‘((father son) (mother daughter)))
(TELL ME MORE ABOUT YOUR MOTHER PLEASE)
* (member ‘mother pairs)
NIL
Thay đổi cách xử lý các đối số
Thông thường, MEMBER thực hiện việc kiểm tra với vị từ EQL – vị từ chỉ hoạt động với ký
hiệu và số có cùng kiểu. Bây giờ chúng ta muốn MEMBER tìm một phần tử xuất hiện trong
danh sách, chúng ta phải chỉ ra là chúng ta cần kiểm tra tính thành viên dùng vị từ EQUAL
thay vì EQL.
Hướng dẫn sử dụng Lisp
45
* (setf pairs ‘((mapple shade) (apple fruit)))
((MAPPLE SHADE) (APPLE FRUIT))
* (member ‘(mapple shade) pairs)
NIL
* (member ‘(mapple shade) pairs :test #’equal)
((MAPPLE SHADE) (APPLE FRUIT))
IV.4 Vị từ NULL và ENDP
Ngoài vị từ LISTP, các vị từ quan trọng nhất trên danh sách là:
Vị từ Mục đích
null
endp
Đối số của vị từ là một danh sách rỗng ?
Đối số của vị từ (phải là danh sách) là một danh sách rỗng ?
Ví dụ:
* (null ‘(this is not empty))
NIL
* (endp ‘(this is not empty))
NIL
* (null ‘())
T
* (endp ‘())
T
* (null ‘this-is-a-symbol)
NIL
* (endp ‘this-is-a-symbol)
ERROR:
ENDP: wrong type argument: THIS-IS-A-SYMBOL
A CONS or NIL was expected
Thông thường ENDP được dùng để kiểm tra danh sách rỗng, và NULL cũng vậy. Tuy
nhiên ENDP dùng với một đối tượng không phải danh sách sẽ sinh ra lỗi. Vì vậy, nên dùng
NULL cho việc kiểm tra dữ liệu nói chung có phải là NIL hay không, hơn là cho việc kiểm tra
danh sách rỗng.
IV.5 Các vị từ xác định kiểu dữ liệu
Trong LISP, kiểu được gán cho dữ liệu chứ không phải cho biến. Ta có thể biết kiểu dữ liệu
của biến nhờ vào các vị từ (prédicat)
Vị từ Mục đích
atom
numberp
symbolp
Đối số có phải là một atom ?
Đối số có phải là một số ?
Đối số có phải là một ký hiệu ?
Hướng dẫn sử dụng Lisp
46
stringp
listp
Đối số có phải là một chuỗi ?
Đối số có phải là một danh sách ?
Ví dụ:
* (atom ‘pi)
T
* (atom pi)
T
* (numberp ‘pi)
NIL
* (numberp pi)
T
* (symbolp ‘pi)
T
* (symbolp pi)
NIL
* (listp ‘pi)
NIL
* (listp pi)
NIL
* (listp ‘(this is a list with pi in it))
T
NIL tương đương với danh sách rỗng
NIL và danh sách rỗng, ( ), hoàn toàn tương đương và thỏa mãn tất cả các vị từ EQ, EQL, và
EQUAL.
* (eq nil ‘())
T
* (eql nil ‘())
T
* (equal nil ‘())
T
Cả NIL và danh sách rỗng đều được in là NIL.
* nil
NIL
* ()
NIL
Chính sự tương đương giữa NIL và () đã gây rắc rối, bởi vì NIL và () đều là ký hiệu và
danh sách. Vì thế, cả (SYMBOLP ‘()) và (LISTP NIL) đều trả về T.
* (atom nil)
T
* (atom ())
Hướng dẫn sử dụng Lisp
47
T
* (symbolp nil)
T
* (symbolp ())
T
* (listp nil)
T
* (listp ())
T
IV.6 Các vị từ trên số
Các vị từ trên số mang ý nghĩa khá rõ ràng:
Vị từ Mục đích
numberp
zerop
plusp
minusp
evenp
oddp
>
<
Đối số có phải là một số ?
Đối số có phải là zero ?
Đối số có phải là một số dương ?
Đối số có phải là một số âm ?
Đối số là số chẵn ?
Đối số là số lẻ ?
Các đối số theo thứ tự giảm dần ?
Các đối số theo thứ tự tăng dần ?
Ví dụ:
* (setf zero 0 one 1 two 2 three 3 four 4)
4
* (setf digits (list zero on0e two three four))
(0 1 2 3 4)
Dùng những giá trị trên, chúng ta có thể xem các vị từ số. NUMBERP kiểm tra xem đối
số của nó có phải là một số hay không.
* (numberp 4)
T
* (numberp four)
T
* (numberp ‘four)
NIL
* (numberp digits)
NIL
* (numberp ‘digits)
NIL
Hướng dẫn sử dụng Lisp
48
ZEROP nhận đối số là một số và kiểm tra nó có phải là zero hay không. ZEROP sẽ phát
sinh lỗi nếu đối số không có kiểu số.
* (zerop zero)
T
* (zerop ‘zero)
ERROR:
ZEROP: wrong type argument: ZERO
A NUMBER was expected.
* (zerop four)
NIL
PLUSP kiểm tra một số có phải là số dương hay không.
* (plusp one)
T
* (plusp (- one))
NIL
* (plusp zero)
NIL
EVENP kiểm tra một số có phải là số chẵn hay không.
* (evenp two)
T
* (evenp (* 9 7 5 3 1))
NIL
* (evenp (* 10 8 6 4 2))
T
Các vị từ > và kiểm tra xem các đối số có giảm
ngặt và < kiểm tra chúng có tăng ngặt hay không. Cả hai vị từ đều có thể nhận một, hai hay
nhiều đối số.
* (> four two)
T
* (>two four)
NIL
* (> three two one)
T
* (> three one two)
NIL
IV.7 Các toán tử logic
1. AND
(and E1 E2 ... En) là sai nếu ít nhất một Ei sai
AND đánh giá các đối số từ trái sang phải và chỉ dừng khi gặp một đối số sai
Nếu mọi thông số đều đúng, AND trả về thông số cuối cùng
Hướng dẫn sử dụng Lisp
49
Ví dụ:
* (setq x ‘a)
A
*x
A
* (and (numberp x) (> x 1) )
NIL
* (and (symbolp x) (list x) )
(A)
2. OR
(or E1 E2 ... En) là sai nếu ít nhất một Ei sai
OR đánh giá các đối số từ trái sang phải và chỉ dừng khi gặp một đối số đúng
Ví dụ:
* (setq x ‘a)
A
*x
A
* (or (numberp x) (> x 1) )
error > A is not a number
* (or (symbolp x) (list x) )
T
3. NOT
NOT đổi giá trị không NIL thành NIL và NIL thành T.
Ví dụ:
* (not nil)
T
* (not t)
NIL
* (not ‘dog)
NIL
* (setf pets ‘(dog cat))
(DOG CAT)
* (member ‘dog pets)
(DOG CAT)
* (not (member ‘dog pets))
NIL
* (member ‘dingo pets)
NIL
* (not (member ‘dingo pets))
Hướng dẫn sử dụng Lisp
50
T
* (and (member ‘dog pets) (member ‘tiger pets))
NIL
* (and (member ‘dog pets) (not (member ‘tiger pets)))
T
Chúng ta nhận thấy rằng NIL và danh sách rỗng – () là hoàn toàn tương đương nhau. Do
đó NOT thật sự làm cùng một công việc như NULL: cả hai đều trả về T nếu đối số là NIL, và
cả hai đều trả về NIL khi đối số là không NIL.
IV.8 Các dạng điều kiện
1. IF, WHEN và UNLESS
(if E1 E2 E3)
Nếu E1 đúng, trả về giá trị E2 nếu không trả về giá trị E3
Ví dụ:
* (if (numberp 1) ‘(a number) ‘(not a number))
(A NUMBER)
* (if (numberp ‘a) ‘(a number) ‘(not a number))
(NOT A NUMBER)
Ở đây, chỉ có một trong hai đối số – đối số thứ hai hoặc thứ ba được đánh giá. IF được
gọi là có điều kiện vì IF phụ thuộc vào giá trị đối số của nó. Có nhiều dạng điều kiện khác phổ
biến IF hơn khi người dùng không cần lắm tính tổng quát của IF. Chi tiết hơn, WHEN được
dùng thay cho IF khi mệnh đề kết quả sai trong IF (biểu thức E3 ở trên) là NIL.
(if E1 E2 nil)
≡
(when E1 E2)
Tương tự như vậy, UNLESS được dùng thay cho IF khi mệnh đề kết quả đúng trong IF
(biểu thức E2 ở trên) là NIL.
(if E1 nil E3)
≡
(when E1 E3)
Thật sự, cả WHEN và UNLESS có số lượng không giới hạn các đối số. Đối số thứ nhất là
biểu thức cần kiểm tra; đối số cuối cùng trả về giá trị; và mọi đối số ở giữa được đánh giá cho
các hiệu ứng lề. Trong ví dụ bên dưới, giá trị của WHEN là NEW-RECORD khi
TEMPRATURE có giá trị lớn hơn HIGH.
* (setf high 98 temprature 102)
102
* (when (> temprature high) ; So sánh TEMPRATURE với HIGH
(setf high temprature) ; Nếu lớn hơn, thay đổi giá trị HIGH
‘new-record) ; Nếu lớn hơn, trả về NEW-RECORD
Hướng dẫn sử dụng Lisp
51
2. COND
(cond (Test1 E1 )
(Test2 E2 )
(Test3 E3 )
(Testn En ) )
≡
(if Test1 (progn E1 )
(if Test2 (progn E2 )
(if Test3 (progn E3 )
(if Testn (progn En )) )
) )
Trong một mệnh đề kiểu (Test1), nếu Test1 đúng, kết quả của cond là giá trị của (Test1)
Ví dụ: Viết hàm trả về kiểu của đối số
* (type-of 1)
FIXNUM
* (type-of a)
SYMBOL
Giải:
(defun type-of (x)
(cond ((null x) ‘null)
((symbolp x) ‘symbolp)
((numberp x) ‘numberp)
((stringp x) ‘stringp)
((consp x) ‘consp)
(t ‘unknown-type) ) )
3. CASE
Các dạng IF, WHEN và UNLESS có thể xem như những trường hợp đặc biệt của COND
vì tất cả đều có thể biểu diễn bằng COND. Chúng ta xem một trường hợp đặc biệt khác,
CASE, có ý nghĩa tương tự như COND. CASE theo sau là dạng khóa (key form) và một dãy
các mệnh đề.
(case Key
(Key1 E11 )
(Key2 E21 )
(Keyn En1 ) )
≡
Hướng dẫn sử dụng Lisp
52
(cond ( (eql Key Key1) E11 )
( (eql Key Key2) E21 )
( (eql Key Key3) E31 )
( (eql Key Keyn) En1 ) )
CASE dùng vị từ EQL và so sánh khóa được đánh giá với các khóa chưa được đánh giá.
Nếu tìm thấy khóa, mệnh đề tương ứng sẽ được thực hiện và tất cả mọi biểu thức trong mệnh
đề được đánh giá.
Chúng ta dùng CASE khi ta muốn có COND trong đó mọi biểu thức kiểm tra tìm một giá
trị trong nhiều khả năng. Trong ví dụ bên dưới, xét dạng COND sau:
* (cond ((eq thing ‘circle) (* pi r r))
((eq thing ‘sphere) (* 4 pi r r)))
Ví dụ trên được viết lại dưới dạng CASE như sau.
* (case thing ;ký hiệu THING sẽ được đánh giá
(circle (* pi r r)) ;ký hiệu CIRCLE chưa được đánh giá
(sphere (* 4 pi r r))) ;ký hiệu SPHERE chưa được đánh giá
Hướng dẫn sử dụng Lisp
53
Chương V. Trừu tượng hóa dữ liệu
Phần này sẽ giới thiệu khái niệm trừu tượng hóa dữ liệu – tiến trình giúp bạn xây dựng các
chương trình lớn và phức tạp trong LISP.
V.1 Các trường của một ký hiệu
Ký hiệu là một đối tượng bao gồm nhiều trường:
¾ CVAL: giá trị của ký hiệu cũng như biến
¾ PNAME: chuỗi ký tự tương ứng với tên của ký hiệu (dùng cho máy in)
¾ FVAL: hàm gắn liền với ký hiệu, trường này không tồn tại trong LISP đơn trị
LISP đa trị (bi-valued) LISP đơn trị (mono-valued)
* (setq + 4)
4
* (+ + 3)
7
* (setq + *)
var + indef
* (setq + 4)
4
*