function - 关于在函数和宏定义中使用结构文字

标签 function random macros common-lisp literals

重要提示:这个问题完全是出于我想学习 Common Lisp 的动机。除了这个自学目标之外,它没有任何不可告人的实际目的。


如果在我的 REPL1 中,我计算以下表达式

CL-USER> (random-state-p
  #S(random-state
       :state #.(make-array 627
                            :element-type '(unsigned-byte 32)
                            :initial-element 7)))

...我得到 T。换句话说,文字

#S(random-state
     :state #.(make-array 627
                          :element-type '(unsigned-byte 32)
                          :initial-element 7))

...评估为有效的随机状态对象。


到目前为止一切顺利。

现在,我尝试定义一个函数 silly-random-state,这样表达式 (silly-random-state 7) 将产生相同的 random-state 对象由上面的文字产生,像这样:

(defun silly-random-state (some-int)
  #S(random-state
       :state #.(make-array 627
                            :element-type '(unsigned-byte 32)
                            :initial-element some-int)))

...但是我的 REPL 将没有它!它甚至不会计算这个 defun!它提示 some-int 未定义2

经过进一步思考,我想人们不能将变量插入文字并期望结果表达式有意义......也许我需要一个这个宏?

然后我尝试定义一个,给定一个整数参数,它将扩展为这样一个random-state对象,如下所示:

(defmacro silly-random-state-macro (some-int)
  `#S(random-state
        :state #.(make-array 627
                             :element-type '(unsigned-byte 32)
                             :initial-element ,some-int)))

同样,我的 REPL 将没有任何内容。上面表达式的评估失败了

Comma inside backquoted structure (not a list or general vector.)


上述两个失败中的每一个都会导致相应的问题:

  1. 我如何定义一个函数 silly-random-state 接受一个整数 SOME-INT 作为参数,并返回 random-state 等于在用整数 SOME-INT 替换 PLACEHOLDER 后给它下面的表达式时 REPL 会产生的结果?
#S(random-state
     :state #.(make-array 627
                          :element-type '(unsigned-byte 32)
                          :initial-element PLACEHOLDER))
  1. 如何定义 silly-random-state-macro 这样(silly-random-state-macro some-int) 扩展为 (1) 中描述的相同 random-state 对象?

(重复一遍,我在这里的动机只是为了更好地理解 Common Lisp3 可以做什么和不能做什么。)


1 SBCL + SLIME/Emacs 在 Darwin 上运行。

2 顺便说一句,我的 REPL 以前从未如此挑剔!例如,它会评估类似 (defun foo () undefined-nonsense) 的内容。诚然,它确实对主体中的 undefined variable 提示了一下,但最终它评估defun

3 事实上,我争论过我是否应该在这个问题的标签中包含 [prng],因为它的主题实际上是文字、函数和宏,以及 CL 的作用其中的默认 PRNG 只是偶然的。我最终选择保留此标签,以防 PRNG 在问题中的作用不像我认为的那么偶然。


编辑:好的,我想我找到了一种方法来定义我在 (1) 中描述的函数;它不是很优雅,但它有效:

(defun silly-random-state (some-int)
  (read-from-string
   (format nil "#S(random-state :state #.(make-array 627 :element-type '(unsigned-byte 32) :initial-element ~A))" some-int)))

最佳答案

你的问题是关于结构文字,但依赖于一个不幸的例子(随机状态)。您通常可以在代码中编写结构文字,但正如在另一个答案中所说,没有办法在结构文字中使用反引号。如果我尝试这样做,则会出现错误:

Comma inside backquoted structure (not a list or general vector.)

您必须围绕这些结构使用构造函数或更高级别的 API。

注入(inject)值的唯一方法是读取时求值,但正如您所注意到的,您需要处于读取阶段,因此需要使用 read-from-string。这可行,但正如您所说,不是很优雅。

此外,这里您已经依赖于 random-state 的特定表示,因此您不妨使用 SBCL 提供的扩展,即 sb-ext:seed-random-state .

因此,这就是您如何编写一个函数来构建给定占位符值的随机状态:

USER> (defun silly-random-state (some-int)
        (sb-ext:seed-random-state (make-array 627
                                              :element-type '(unsigned-byte 32)
                                              :initial-element some-int)))
SILLY-RANDOM-STATE
USER> (silly-random-state 5)
#S(RANDOM-STATE :STATE #.(MAKE-ARRAY 627 :ELEMENT-TYPE '(UNSIGNED-BYTE 32)
                                     :INITIAL-CONTENTS
                                     '(0 2567483615 624 2147483648 2465029273 ...)))

现在您有了一个不依赖于读取时宏的常规函数​​,一旦您将它加载到您的环境中,您就可以在不同的评估点、读取时、宏中等调用它。这是不特定于随机状态,一般来说,您应该尝试编写常规函数,然后才选择它们何时执行

但在这里你必须小心,因为随机状态在使用时会发生变异。

在下面的示例中,我绑定(bind)了 *random-state* 以强制为 random 使用特定状态(我也可以将它直接传递给 random ,这里不重要)。多次调用生成的函数以显示结果如何变化。

这里的函数每次都重新创建一个新的状态,结果总是一样的:

USER> (lambda () (let ((*random-state* (silly-random-state 3)))
                   (random 10)))
#<FUNCTION (LAMBDA ()) {536DDB4B}>
USER> (loop repeat 5 collect (funcall *))
(7 7 7 7 7)

下面,由于读取时评估,一个状态文字被计算并注入(inject)到源代码中,但请注意,随着时间的推移会产生不同的值:

USER> (lambda () (let ((*random-state* #.(silly-random-state 3)))
                   (random 10)))
#<FUNCTION (LAMBDA ()) {536E5C7B}>
USER> (loop repeat 5 collect (funcall *))
(7 0 9 3 6)
                

这是因为 random 改变了状态。但是由于状态现在是文字,我们进入了未定义行为的领域。这在 3.7.1 Modification of Literal Objects 中非常明确:

The consequences are undefined if literal objects are destructively modified. For this purpose, the following operations are considered destructive:

random-state: using it as an argument to the function random.

所以您不应该尝试在代码中使用此类文字对象。

加载时间值

同样,您可以尝试使用 LOAD-TIME-VALUE将随机状态播种到特定状态。下面我定义了 roll-1d6,它从 1 到 6(含)之间选择一个数字。目的是无论全局随机状态如何,骰子在加载程序时始终表现相同:

USER> (defun roll-1d6 ()
      (let ((*random-state* (load-time-value (silly-random-state 3))))
        (1+ (random 6))))
ROLL-1D6
USER> (roll-1d6)
5 (3 bits, #x5, #o5, #b101)
USER> (roll-1d6)
5 (3 bits, #x5, #o5, #b101)
USER> (roll-1d6)
1 (1 bit, #x1, #o1, #b1)
USER> (roll-1d6)
2 (2 bits, #x2, #o2, #b10)
USER> (roll-1d6)
4 (3 bits, #x4, #o4, #b100)

如果我重新定义它(以不同但等效的方式),当我重新加载函数时状态会重置,并且会计算相同的数字:

USER> (defun roll-1d6 () (1+ (random 6 (load-time-value (silly-random-state 3)))))
WARNING: redefining PARROT.USER::ROLL-1D6 in DEFUN
ROLL-1D6
USER> (roll-1d6)
5 (3 bits, #x5, #o5, #b101)
USER> (roll-1d6)
5 (3 bits, #x5, #o5, #b101)
USER> (roll-1d6)
1 (1 bit, #x1, #o1, #b1)
USER> (roll-1d6)
2 (2 bits, #x2, #o2, #b10)
USER> (roll-1d6)
4 (3 bits, #x4, #o4, #b100)

但是,即使 load-time-value 允许生成的对象是可变的:

If [read-only-p is] nil (the default), the result must be neither copied nor coalesced; it must be considered to be potentially modifiable data.

它还说:

The result of this compile-time evaluation is treated as a literal object in the compiled code.

还有:

If two lists (load-time-value form) that are the same under equal but are not identical are evaluated or compiled, their values always come from distinct evaluations of form. Their values may not be coalesced unless read-only-p is t.

所以我不是 100% 确定这个例子是明确定义的,我认为如果有多个使用相同参数的调用,编译器不允许构建和重用单个对象,例如(silly-random-state 3),但结果对象是 random-state 类型的事实可能会导致对其进行未定义的进一步操作(?),这不会例如,用户定义的类就不是这种情况。

放过defun

您还可以按如下方式在函数中预先计算状态:

(let ((state (silly-random-state 3)))
  (defun roll-1d6 ()
    (1+ (random 6) state)))

关于function - 关于在函数和宏定义中使用结构文字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70097468/

相关文章:

java - 从我自己的 java 应用程序中运行 ImageJ 宏

c++ - 好友功能看不到私有(private)成员

java - Java中的继承,无法访问子类函数/方法

python - Numpy 随机选择,仅沿一个轴替换

javascript - 如何从 JavaScript 的 Math.random 生成包含边界的 Pareto 随机整数

c++ - 在 (x,y) 平面中选取随机格点

javascript - 使用函数构造函数代替 eval() 来评估 AJAX 结果

javascript - 将 JQuery 函数移至函数内部,然后从多个函数访问它

unit-testing - 如何在使用 throw-with-msg 的测试中捕获 IllegalArgumentException?

macros - Go 中的 C 风格宏