【问题标题】:Return from format inside loop从循环内的格式返回
【发布时间】:2015-09-20 07:31:51
【问题描述】:

我是一个 Lisp 菜鸟,在理解 loopformat 组合时的工作方式时有些困难。

这符合我的预期:

(loop for i upto 2 do (format t "~a" "Badger")) ==> BadgerBadgerBadger NIL

这不是:

(loop for i upto 2 do (format nil "~a" "Badger")) ==> NIL

为什么第二个循环不返回 BadgerBadgerBadger ?我必须写什么代码才能给出这个返回值?

【问题讨论】:

  • 第一个循环也不返回 BadgerBadgerBadger。它返回 nil。第二个也是如此。第一个,因为format 正在写入标准输出,所以打印 Badger 3 次。第二个,因为格式返回一个字符串,然后被丢弃(因为你没有保存它),不会产生任何输出。

标签: loops format lisp common-lisp


【解决方案1】:

返回值和打印值之间有一个重要的区别。有时这在 REPL 中可能会造成混淆,因为默认情况下会打印返回值:

CL-USER> (+ 1 1)    ; form *returns* 2
2                   ; and return value (2) is printed
CL-USER> (let () 
           (+ 1 1)  ; (+ 1 1) still returns 2, but
           nil)     ; the return value of the (let ...) is NIL
NIL                 ; so NIL is printed

现在,格式可以做一些不同的事情,这取决于它的第一个参数。如果它的第一个参数是 t 或流,那么它将输出写入流并返回 nil:

CL-USER> (format t "hello")
hello                         ; printed output
NIL                           ; return value from format

CL-USER> (let ()             
           (format t "hello") ; will print "hello"
           42)                ; but the whole form returns 42
hello                         ; printed output
42                            ; printed return value

当使用 nil 作为第一个参数调用 format 时,它会返回它生成为字符串的输出:

CL-USER> (format nil "hello")
"hello"                           ; return value, not printed output

CL-USER> (let ()
           (format nil "hello")   ; returns "hello"
           42)                    ; but the whole form returns 42
42                                ; printed return value

现在,您可以collect循环结果,听起来您想使用format生成一个字符串,然后收集这些字符串:

CL-USER> (loop for i upto 2 collect i)
(0 1 2)

CL-USER> (loop for i upto 2 collect (* 8 i))
(0 8 16)

CL-USER> (loop for i upto 2 collect (format nil "string number ~a" i))
("string number 0" "string number 1" "string number 2")

【讨论】:

  • 感谢您的完整解释,具有各种第一个参数的格式行为并不是造成我困惑的原因,但您帮助我更准确地指出了它 - 为什么循环不返回格式生成的字符串作为返回值在里面?
  • 您生成的不是一个字符串,而是三个:每次迭代一个。循环不会自动收集do 子句的结果;这不是它的定义方式。你有没有遇到过让你不相信的事情。
  • 我写了一些我上面指出的东西(即循环内的格式化操作,在循环完成时作为一个字符串返回)。在 REPL 和 C-c C-c (似乎在 Aquamacs 上存储独立版本的函数)之间,这是通过具有 (let (...) (loop for i up to ... when ... do (format nil "~a" ...) else do (format nil "~a" ...))) 结构的东西实现的,因为我没有尝试其他方法。重新加载生成的代码 NIL 代替。我不确定所需的行为缺少什么,它不是 with-output-to-string 或 collect,因为我不知道它们。
  • 除此之外,我的理解是,在循环之类的构造内部可以执行许多表单,返回值应该是最后评估的表单,如您的 (let (format nil "hello" ) 42) 例子。为什么循环不返回格式发生 3 次的结果(在我的问题中的示例中),即使它确实正确连接了字符串,正如当 nil 的第一个参数更改为 t 而不是 nil 时所看到的那样?
  • @Sam 因为LOOP 有明确的关键字来积累数据(sumcollect,...)。
【解决方案2】:

这取决于format 函数的第一个参数,称为destination(参见manual):

如果destination 是字符串、流或t,则结果为nil。否则,结果是一个包含“输出”的字符串

在第一种情况下,format 写入标准输出(您会看到三个“Bugger”)并返回NIL。但是你看不到那个值,而是loop返回的值,也就是NIL。事实上,LOOP 中没有任何子句(如RETURN)返回与NIL 不同的内容。

第二种情况,format返回字符串,但是循环的值又是NIL,是整个表单的结果。

如果你想返回格式的结果,你可以,例如,写:

(with-output-to-string (s)
   (loop for i upto 2 do (format s "~a" "Badger")))

这样format函数“写入”到字符串流s,由with-output-to-string返回。

【讨论】:

  • 好吧到目前为止一切都很好,那么我该如何重写我的循环以便它返回格式的结果?
  • 我更新了答案以展示如何从格式中“返回”一个值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
  • 1970-01-01
  • 2015-04-22
  • 2018-02-21
  • 1970-01-01
  • 2015-11-09
  • 2016-05-22
相关资源
最近更新 更多