Chúng tôi đã huấn luyện bạn về các biến và hàm số, và bây giờ ta lội vào "vũng lầy tăm tối" về các danh sách của Scheme.
Trước khi nói thêm về danh sách, bạn cần phải biết sự khác biệt giữa các giá trị nguyên tử (atomic value) và các danh sách.
Bạn đã nhìn thấy các giá trị nguyên tử khi chúng ta khởi động các biến trong phần trước. Một giá trị nguyên tử là một giá trị duy nhất. Vì thế, ví dụ, chúng ta có thể gán biến "x" giá trị duy nhất là 8 trong câu lệnh sau:
(let* ( (x 8) ) x)
(Chúng ta đã thêm biểu thức x vào cuối để in ra giá trị được gán cho x-- thông thường bạn không cần làm điều này. Lưu ý cách let* hoạt động giống như một hàm số. Giá trị của câu lệnh cuối cùng là giá trị được trả về).
Một biến có thể tham chiếu (refer) đến một danh sách các giá trị. Để gán cho biến x danh sách các giá trị 1, 3, 5, chúng ta sẽ gõ:
(let* ( (x '(1 3 5))) x)
Hãy thử cả hai câu lệnh trên vào cửa sổ console Script-Fu và để ý xem nó trả lời thế nào. Khi bạn gõ câu lệnh đầu tiên vào, nó chỉ đơn giản trả lời kết quả:
8
Tuy nhiên, khi bạn gõ câu lệnh kia, nó trả lời bằng kết quả sau:
(1 3 5)
Khi nó trả lời bằng giá trị 8 thì nó thông báo cho bạn rằng x chứa giá trị nguyên tử 8. Tuy nhiên khi nó trả lời bằng (1 3 5) thì nó thông báo cho bạn rằng x không chứa một giá trị duy nhất, mà một danh sách các giá trị. Lưu ý rằng không có các dấu phẩy trong phép khai báo hoặc phép gán của chúng ta trong danh sách, cũng không có kết quả in ra.
Cú pháp để định nghĩa một danh sách là:
'(a b c)
trong đó a, b, và c là các chữ (literal). Chúng ta sử dụng dấu móc đơn (') để chỉ ra rằng những gì ở trong ngoặc đơn là một danh sách các giá trị chữ, chứ không phải là một hàm số hoặc biểu thức.
Một danh sách rỗng có thể được định nghĩa như sau:
'()
hoặc đơn giản là:
()
Danh sách có thể chứa các giá trị nguyên tử cũng như các danh sách khác::
(let*
(
(x
'("The Gimp" (1 2 3) ("is" ("great" () ) ) )
)
)
x
)
Lưu ý rằng sau dấu móc đơn đầu tiên, bạn không còn cần dùng đến dấu móc đơn khi định nghĩa các danh sách bên trong. Hãy tiếp tục và chép câu lệnh trên vào cửa sổ console của Script-Fu và xem nó trả lại gì.
Bạn sẽ lưu ý thấy rằng kết quả trả về không phải là một danh sách các giá trị nguyên tử, duy nhất; mà đúng ra là một danh sách của một chữ ("The Gimp"), danh sách (1 2 3), ...
Điều hữu ích là nghĩ về danh sách như là sự phối hợp của một cái "đầu" và một cái "đuôi". Đầu là yếu tố đầu tiên của danh sách, và đuôi là phần còn lại của danh sách. Bạn sẽ hiểu tại sao điều này quan trọng khi chúng ta thảo luận về việc làm sao thêm vào các danh sách và làm sao truy nhập vào các yếu tố (element) trong danh sách.
Một trong những hàm số phổ biến mà bạn hay gặp là hàm cons. Nó lấy một giá trị và gắn (prepend???) vào đối số thứ hai của nó, một danh sách. Trong phần trước, tôi đã đề nghị là bạn nghĩ về danh sách như được tổng hợp bởi một yếu tố (đầu) và phần còn lại của danh sách (đuôi) Đây chính là cách hàm số cons hoạt động -- nó thêm một yếu tố vào phần đầu của một danh sách. Do vậy bạn có thể tạo ra một danh sách như sau:
(cons 1 '(2 3 4) )
Kết quả là danh sách (1 2 3 4).
Bạn cũng có thể tạo ra một danh sách chỉ gồm một yếu tố:
(cons 1 () )
Bạn có thể sử dụng các biến đã được khai báo trước đây ở chỗ các chữ, nếu bạn muốn.
Để định nghĩa một danh sách bao gồm các chữ hoặc các biến đã được khai báo, dùng hàm list:
(list 5 4 3 a b c)
Câu lệnh này sẽ tổng hợp và trả về một danh sách chứa các giá trị do các biến a, b và c lưu giữ. Ví dụ:
(let* (
(a 1)
(b 2)
(c 3)
)
(list 5 4 3 a b c)
)
Mã này tạo ra danh sách 5 4 3 1 2 3).
Để truy nhập các giá trị trong một danh sách, dùng hàm số car và cdr, chúng tương ứng trả về yếu tố đầu tiên của danh sách và phần còn lại của danh sách. Những hàm này ngắt danh sách thành kiến trúc đầu::đuôi như tôi đã đề cập trước đây.
Hàm car trả về yếu tố đầu tiên của danh sách (đầu của danh sách). Danh sách phải không rỗng (non-null). Do vậy, câu lệnh sau trả về yếu tố đầu tiên của danh sách:
(car '("first" 2 "third"))
đó là:
"first"
Hàm cdr trả về phần còn lại của danh sách nằm sau yếu tố đầu tiên (đuôi của danh sách). Nếu chỉ có một yếu tố trong danh sách, nó trả về một danh sách rỗng.
(cdr '("first" 2 "third"))
trả về:
(2 "third")
trong đó câu lệnh sau:
(cdr '("one and only"))
trả về:
()
Được rồi, tuyệt, chúng ta có thể lấy yếu tố đầu tiên trong một danh sách cũng như phần còn lại của danh sách, nhưng làm thế nào chúng ta truy nhập vào yếu tố thứ hai, thứ ba hoặc các yếu tố khác trong một danh sách? Co vài hàm số "tiện lợi" khác để truy nhập, ví dụ, phần đầu của đẩu của đuôi của một danh sách (caadr), đuôi của đuôi của một danh sách (cddr), ...
Quy ước đặt tên căn bản là dễ dàng: Các chữ a và d biểu thị cho các đầu và đuôi của các danh sách, do vậy:
(car (cdr (car x) ) )
có thể được viết thành:
(cadar x)
Để xem đầy đủ các hàm danh sách, tham khảo phần Phụ lục, trong đó liệt kê các hàm số trong phiên bản Scheme mà Script-Fu sử dụng.
Để có chút ít thực tập với các hàm truy nhập danh sách, hãy thử gõ câu lệnh sau (tất cả trên một dòng nếu bạn dùng console); sử dụng các "biến thể" khác nhau của car và cdr để truy nhập các yếu tố khác nhau trong danh sách:
(let* (
(x '( (1 2 (3 4 5) 6) 7 8 (9 10) )
)
)
; place your car/cdr code here
)
Hãy thử truy nhập số 3 trong danh sách bằng cách chỉ dùng hai phép gọi hàm. Nếu bạn có thể làm được vậy, bạn đang trên con đường trở thành một Chuyên gia Script-Fu!
|
Ghi chú |
|---|---|
|
Trong Scheme, dấu chấm phẩy (";") đánh dấu một ghi chú (comment). Nó, và bất kỳ thứ gì theo sau nó trên cùng một hàng, bị bỏ qua trong trình diễn giải tập lệnh, do vậy bạn có thể sử dụng điều này để thêm các ghi chú "đánh thức" (jog) trí nhớ của bạn khi đọc tập lệnh sau này. |
|