LISP - Xử lý lỗi
Trong thuật ngữ LISP phổ biến, các ngoại lệ được gọi là điều kiện.
Trên thực tế, các điều kiện tổng quát hơn các ngoại lệ trong các ngôn ngữ lập trình truyền thống, bởi vì condition đại diện cho bất kỳ sự xuất hiện, lỗi hoặc không, có thể ảnh hưởng đến các cấp độ khác nhau của ngăn xếp lệnh gọi hàm.
Cơ chế xử lý điều kiện trong LISP, xử lý các tình huống như vậy theo cách mà các điều kiện được sử dụng để báo hiệu cảnh báo (giả sử bằng cách in cảnh báo) trong khi mã cấp trên trên ngăn xếp cuộc gọi có thể tiếp tục công việc của nó.
Hệ thống xử lý điều kiện trong LISP có ba phần:
- Báo hiệu một điều kiện
- Xử lý tình trạng
- Khởi động lại quá trình
Xử lý một điều kiện
Chúng ta hãy lấy một ví dụ về việc xử lý một điều kiện phát sinh từ điều kiện chia cho 0, để giải thích các khái niệm ở đây.
Bạn cần thực hiện các bước sau để xử lý một điều kiện:
Define the Condition - "Một điều kiện là một đối tượng mà lớp của nó chỉ ra bản chất chung của điều kiện và dữ liệu thể hiện của nó mang thông tin về chi tiết của các trường hợp cụ thể dẫn đến điều kiện được báo hiệu".
Macro xác định điều kiện được sử dụng để xác định một điều kiện, có cú pháp sau:
(define-condition condition-name (error)
((text :initarg :text :reader text))
)
Các đối tượng điều kiện mới được tạo bằng macro MAKE-CONDITION, macro này sẽ khởi tạo các vị trí của điều kiện mới dựa trên :initargs tranh luận.
Trong ví dụ của chúng tôi, đoạn mã sau xác định điều kiện:
(define-condition on-division-by-zero (error)
((message :initarg :message :reader message))
)
Writing the Handlers- một trình xử lý điều kiện là một mã được sử dụng để xử lý điều kiện được báo hiệu trên đó. Nó thường được viết bằng một trong các hàm cấp cao hơn gọi là hàm sửa lỗi. Khi một điều kiện được báo hiệu, cơ chế báo hiệu sẽ tìm kiếm một trình xử lý thích hợp dựa trên lớp của điều kiện.
Mỗi trình xử lý bao gồm -
- Nhập thông số, cho biết loại điều kiện mà nó có thể xử lý
- Một hàm nhận một đối số duy nhất, điều kiện
Khi một điều kiện được báo hiệu, cơ chế báo hiệu sẽ tìm trình xử lý được thiết lập gần đây nhất tương thích với loại điều kiện và gọi hàm của nó.
Macro handler-casethiết lập một trình xử lý điều kiện. Dạng cơ bản của một trường hợp xử lý -
(handler-case expression error-clause*)
Trong đó, mỗi mệnh đề lỗi có dạng -
condition-type ([var]) code)
Restarting Phase
Đây là mã thực sự khôi phục chương trình của bạn khỏi lỗi và trình xử lý điều kiện sau đó có thể xử lý một điều kiện bằng cách gọi khởi động lại thích hợp. Mã khởi động lại thường được đặt trong các chức năng cấp trung bình hoặc cấp thấp và các trình xử lý điều kiện được đặt vào các cấp trên của ứng dụng.
Các handler-bindmacro cho phép bạn cung cấp chức năng khởi động lại và cho phép bạn tiếp tục ở các chức năng cấp thấp hơn mà không cần tháo ngăn xếp lệnh gọi hàm. Nói cách khác, luồng điều khiển sẽ vẫn nằm trong chức năng cấp thấp hơn.
Dạng cơ bản của handler-bind như sau -
(handler-bind (binding*) form*)
Trong đó mỗi ràng buộc là một danh sách sau:
- một loại điều kiện
- một hàm xử lý của một đối số
Các invoke-restart macro tìm và gọi hàm khởi động lại liên kết gần đây nhất với tên được chỉ định làm đối số.
Bạn có thể khởi động lại nhiều lần.
Thí dụ
Trong ví dụ này, chúng tôi chứng minh các khái niệm trên bằng cách viết một hàm có tên là hàm chia, hàm này sẽ tạo ra một điều kiện lỗi nếu đối số số chia là 0. Chúng tôi có ba hàm ẩn danh cung cấp ba cách để thoát khỏi nó - bằng cách trả về giá trị 1, bằng cách gửi ước số 2 và tính toán lại hoặc bằng cách trả về giá trị 1.
Tạo một tệp mã nguồn mới có tên main.lisp và nhập mã sau vào đó.
(define-condition on-division-by-zero (error)
((message :initarg :message :reader message))
)
(defun handle-infinity ()
(restart-case
(let ((result 0))
(setf result (division-function 10 0))
(format t "Value: ~a~%" result)
)
(just-continue () nil)
)
)
(defun division-function (value1 value2)
(restart-case
(if (/= value2 0)
(/ value1 value2)
(error 'on-division-by-zero :message "denominator is zero")
)
(return-zero () 0)
(return-value (r) r)
(recalc-using (d) (division-function value1 d))
)
)
(defun high-level-code ()
(handler-bind
(
(on-division-by-zero
#'(lambda (c)
(format t "error signaled: ~a~%" (message c))
(invoke-restart 'return-zero)
)
)
(handle-infinity)
)
)
)
(handler-bind
(
(on-division-by-zero
#'(lambda (c)
(format t "error signaled: ~a~%" (message c))
(invoke-restart 'return-value 1)
)
)
)
(handle-infinity)
)
(handler-bind
(
(on-division-by-zero
#'(lambda (c)
(format t "error signaled: ~a~%" (message c))
(invoke-restart 'recalc-using 2)
)
)
)
(handle-infinity)
)
(handler-bind
(
(on-division-by-zero
#'(lambda (c)
(format t "error signaled: ~a~%" (message c))
(invoke-restart 'just-continue)
)
)
)
(handle-infinity)
)
(format t "Done."))
Khi bạn thực thi mã, nó trả về kết quả sau:
error signaled: denominator is zero
Value: 1
error signaled: denominator is zero
Value: 5
error signaled: denominator is zero
Done.
Ngoài 'Hệ thống điều kiện', như đã thảo luận ở trên, Common LISP cũng cung cấp các chức năng khác nhau có thể được gọi để báo hiệu lỗi. Tuy nhiên, việc xử lý lỗi khi được báo hiệu lại phụ thuộc vào việc triển khai.
Chức năng báo hiệu lỗi trong LISP
Bảng sau đây cung cấp các chức năng thường được sử dụng báo hiệu cảnh báo, ngắt, lỗi không nghiêm trọng và nghiêm trọng.
Chương trình người dùng chỉ định một thông báo lỗi (một chuỗi). Các chức năng xử lý thông báo này và có thể / có thể không hiển thị nó cho người dùng.
Các thông báo lỗi phải được tạo bằng cách áp dụng format hàm, không được chứa ký tự dòng mới ở đầu hoặc cuối, và không cần chỉ ra lỗi, vì hệ thống LISP sẽ xử lý chúng theo kiểu ưa thích của nó.
Sr.No. | Chức năng và Mô tả |
---|---|
1 | error format-string & rest args Nó báo hiệu một lỗi nghiêm trọng. Không thể tiếp tục từ loại lỗi này; do đó lỗi sẽ không bao giờ quay trở lại trình gọi của nó. |
2 | cerror continue-format-string error-format-string & rest args Nó báo hiệu một lỗi và đi vào trình gỡ lỗi. Tuy nhiên, nó cho phép chương trình được tiếp tục từ trình gỡ lỗi sau khi giải quyết lỗi. |
3 | warn format-string & rest args nó in một thông báo lỗi nhưng thường không đi vào trình gỡ lỗi |
4 | break& chuỗi định dạng tùy chọn & args còn lại Nó in thông báo và đi trực tiếp vào trình gỡ lỗi, mà không cho phép bất kỳ khả năng nào bị chặn bởi các phương tiện xử lý lỗi được lập trình |
Thí dụ
Trong ví dụ này, hàm thừa tính toán giai thừa của một số; tuy nhiên, nếu đối số là phủ định, nó sẽ tạo ra một điều kiện lỗi.
Tạo một tệp mã nguồn mới có tên main.lisp và nhập mã sau vào đó.
(defun factorial (x)
(cond ((or (not (typep x 'integer)) (minusp x))
(error "~S is a negative number." x))
((zerop x) 1)
(t (* x (factorial (- x 1))))
)
)
(write(factorial 5))
(terpri)
(write(factorial -1))
Khi bạn thực thi mã, nó trả về kết quả sau:
120
*** - -1 is a negative number.