Restarts in Common Lisp

栏目: IT技术 · 发布时间: 4年前

内容简介:I have been readingWriting a Lisp-descended language professionally, and also living inside Emacs, I had dabbled in Common Lisp before, but I still found something I was not aware of, restarts. I do not think that this is a particularly well known feature

I have been reading Practical Common Lisp by Peter Seibel over the weekend, which is an excellent introduction to Common Lisp, showcasing its power by writing real programs. If you are interested in Lisp or programming languages at all, I recommend at least skimming it, it is free to read online.

Writing a Lisp-descended language professionally, and also living inside Emacs, I had dabbled in Common Lisp before, but I still found something I was not aware of, restarts. I do not think that this is a particularly well known feature outside the Lisp world, so I would like to spread awareness, as I think it is a particularly interesting take on error handling.

The book explains restarts using a mocked parser, which I will slightly modify for my example. Imagine you are writing an interpreter/compiler for a language. On the lowest level you are parsing lines to some internal representation:

(define-condition invalid-line-error (error)
  ((line :initarg :line :reader line)))

(defun parse-line (line)
  (if (valid-line-p line)
      (to-ir line)
    (error 'invalid-line-error :line line)))
We define a condition,

While Common Lisp calls them “conditions”, I will stick with “error” in this post, because I don’t think “condition” is very self-explanatory if you’re not familiar with the language already. Please forgive my inaccuracy.

which is very similar to an exception object with metadata in other languages, and a function which attempts to parse a single line.

This is assuming of course that a line always represents a complete parsable entity, but this is only an example after all.

If it turns out that the line is invalid, it throws an error up the stack. We attach the line encountered, in case we want to use it for error reporting.

Now imagine your parser is used in two situations: there is a compiler, and a REPL. For the compiler, you would like to abort at the first invalid line you encounter, which is what we are currently set up to do. But for the REPL, you would like to ignore the line and just continue with the next line. I’m not saying that is necessarily a good idea, but it is something some REPLs do, for example some Clojure REPLs.

To ignore a line, we would have to either do it on a low-level, return nil instead of throwing and filter out nil values up the stack. Catching the error will not help us a lot, because at that point we have lost our position in the file already, or have we?

The next layer up is parsing a collection of lines:

(defun parse-lines (lines)
  (loop for line in lines
        for entry = (restart-case
                     (parse-line line)
                     (skip-line () nil))
        when entry collect it))
This is where the magic begins. The loop construct just loops over the lines, applies parse-line to every element of the list, and returns a list containing all results which are not nil . The feature I am showcasing in this post is restart-case . Think of it this way: it does not catch an error, but when the stack starts unwinding

Technically not unwinding yet, at least not in Common Lisp.

because we threw an error in parse-line , it registers a possible restart-position. If the error is caught at some point,

If it isn’t caught, you will get dropped into the debugger, which also gives you the option to restart.

the error handler can choose to restart at any restart-point that has been registered down the stack.

Now let us have a look at the callers:

(defun parse-compile (lines)
  (handler-case
      (parse-lines lines)
    (invalid-line-error (e)
                        (print-error e))))

(defun parse-repl (lines)
  (handler-bind ((invalid-line-error
                  #'(lambda (e)
                      (invoke-restart 'skip-line))))
    (parse-lines lines)))

There is a lot to unpack here. The compiler code is using handler-case , which is comparable to catch in other languages. It unwinds the stack to the current point and runs the error handling code, in this case print-error .

Because we do not actually want to unwind the stack all the way, but resume execution inside the loop in parse-lines , we use a different construct, handler-bind , which automatically catches invalid-line-error and invokes the skip-line restart. If you scroll up to parse-lines now, you will see that the restart clause says, if we restart here, just return nil , and nil will be filtered on the very next line by when entry .

The elegance here is the split of error handling code, and decisions about which error handling approach to take. You can register a lot of different restart-case statements throughout the stack, and let the caller decide if some errors are okay to ignore, without the caller having to have intricate knowledge of the lower-level code. It does need to know about the registered restart-case statements though, at least by name.

If you want to learn more about this, make sure to have a look at the book, it goes into much more detail than I did here.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

集体智慧编程

集体智慧编程

Toby Segaran / 莫映、王开福 / 电子工业出版社 / 2015-3 / 79.00元

《集体智慧编程》以机器学习与计算统计为主题背景,专门讲述如何挖掘和分析Web 上的数据和资源,如何分析用户体验、市场营销、个人品味等诸多信息,并得出有用的结论,通过复杂的算法来从Web 网站获取、收集并分析用户的数据和反馈信息,以便创造新的用户价值和商业价值。全书内容翔实,包括协作过滤技术(实现关联产品推荐功能)、集群数据分析(在大规模数据集中发掘相似的数据子集)、搜索引擎核心技术(爬虫、索引、查......一起来看看 《集体智慧编程》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具