使用gensym和macrolet构建哈希表

时间:2011-12-25 12:21:40

标签: lisp common-lisp clisp gnu-common-lisp

我正在尝试在read时构建哈希表(以及其他操作)。我不希望哈希表具有全局范围(但是),所以我用宏和gensym来做这个。在宏x中,我定义了一个类似于s的宏setf,但在哈希表中定义了一个条目,而不是在某个地方定义一个符号。它爆炸了。我想我理解错误信息,但我该如何使它工作?

代码:

#!/usr/bin/clisp -repl

(defmacro x (&rest statements)
  (let ((config-variables (gensym)))
    `(macrolet ((s (place value)
                  (setf (gethash 'place ,config-variables) value)))
       (let ((,config-variables (make-hash-table :test #'eq)))
         (progn ,@statements)
         ,config-variables))))

(defun load-config ()
  (let ((config-file-tree (read *standard-input*)))
    (eval config-file-tree)))

(defun load-test-config ()
  (with-input-from-string (*standard-input* "(x (s fred 3) (s barney 5))")
    (load-config)))

(load-test-config)

输出:

*** - LET*: variable #:G12655 has no value
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead of #:G12655.
STORE-VALUE    :R2      Input a new value for #:G12655.
SKIP           :R3      skip (LOAD-TEST-CONFIG)
STOP           :R4      stop loading file /u/asterisk/semicolon/build.l/stackoverflow-semi

2 个答案:

答案 0 :(得分:4)

猜猜比尔可能真正想要什么。

假设他希望将某些键映射到某些值作为文件中的配置。

这是程序方式。

  • 打开数据流
  • 将其读作s-expression
  • 遍历数据并填写哈希表

示例代码:

(defun read-mapping (&optional (stream *standard-input*))
  (destructuring-bind (type &rest mappings) (read stream)
    (assert (eq type 'mapping))
    (let ((table (make-hash-table)))
      (loop for (key value) in mappings
            do (setf (gethash key table) value))
      table)))

(defun load-config ()
  (read-mapping))

(defun load-test-config ()
  (with-input-from-string (*standard-input* "(mapping (fred 3) (barney 5))")
    (load-config)))

(load-test-config)

使用:

CL-USER 57 > (load-test-config)
#<EQL Hash Table{2} 402000151B>

CL-USER 58 > (describe *)

#<EQL Hash Table{2} 402000151B> is a HASH-TABLE
BARNEY      5
FRED        3

优点:

  • 没有宏
  • 数据未在源代码和生成的源代码中编码
  • 没有评估(安全!)通过EVAL需要
  • 没有对象代码膨胀通过扩展到更大代码的宏
  • 功能抽象
  • 更容易理解和调试

或者,我会为{编写一个读宏,以便将{(fred 3) (barney 5)}直接读作哈希表。


如果您想要计算值:

(defun make-table (mappings &aux (table (make-hash-table)))
  (loop for (key value) in mappings
        do (setf (gethash key table) (eval value)))
  table)

CL-USER 66> (describe (make-table '((fred (- 10 7)) (barney (- 10 5)))))

#<EQL Hash Table{2} 4020000A4B> is a HASH-TABLE
BARNEY      5
FRED        3

将其变成宏:

(defmacro defmapping (&body mappings)
  `(make-table ',mappings))

(defmapping
  (fred 3)
  (barney 5))

答案 1 :(得分:3)

macrolet中,您也可以定义宏,因此通常的规则适用,即您必须反引用表达式,这些表达式将在运行时进行评估。像这样:

(defmacro x (&rest statements)
  (let ((config-variables (gensym)))
    `(macrolet ((s (place value)
                 `(setf (gethash ',place ,',config-variables) ,value)))
      (let ((,config-variables (make-hash-table :test #'eq)))
        (progn ,@statements)
        ,config-variables))))