AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 76956971
Accepted
Antonio Solana
Antonio Solana
Asked: 2023-08-23 05:31:24 +0800 CST2023-08-23 05:31:24 +0800 CST 2023-08-23 05:31:24 +0800 CST

为什么使用“let”时“setf”不起作用?

  • 772

setfwhen 与 结合的行为let让我感到困惑。在这里,setf不会更改列表中的第二个值。

(defun to-temp-field (lst)
  (let ((old-value (nth 2 lst))
        (new-value 'hello))
    (progn
      (setf old-value new-value)
      lst)))

但如果我不这样做let,它会改变:

(defun to-temp-field (lst)
  (let ((new-value 'hello))
    (progn
      (setf (nth 2 lst) new-value)
      lst)))

是什么导致了这种行为?

common-lisp
  • 4 4 个回答
  • 37 Views

4 个回答

  • Voted
  1. Best Answer
    Silvio Mayolo
    2023-08-23T06:22:52+08:002023-08-23T06:22:52+08:00

    Common Lisp 是最早展示共享调用的语言之一,这种范例现在在动态类型和高级编程语言中极为常见。在共享调用语言中,绑定到变量的值隐式地进行某种浅复制,采用最上面的指针或数据的新版本,但保留任何嵌套数据相同。

    在 Lisp 中,这种区别实际上更容易看出。顶层 cons 单元会被复制,但任何嵌套指针都会被保留。

    (defun to-temp-field (lst)
      (let ((old-value (nth 2 l))
            (new-value 'hello))
        (progn
          (setf old-value new-value)
          lst)))
    

    这里,old-value不是对列表中位置的引用。它是列表位置 2 处的值的浅表副本。正如卡奇克的回答所示,setf实际上只是一个非常复杂的宏,宏根据它们所看到的语法来调度它们的行为。所以setf它的第一个参数是old-value。这是在编译时发生的,因此setf无法知道它是old-value来自列表还是来自调用nth,因此它只是设置局部变量(基本上使用setq)。

    现在,考虑你的第二个例子,

    (defun to-temp-field (lst)
      (let ((new-value 'hello))
        (progn
          (setf (nth 2 lst) new-value)
          lst)))
    

    在这里,setf将(nth 2 lst)其视为第一个参数。这不是局部变量。这实际上是一个类似函数调用的表达式。正如我们在hyperspec中看到的,nth实际上有两个条目:一个用于正常函数调用,另一个用于在setf. 那是因为,就像我们可以将普通函数定义为

    (defun nth (n lst)
      ...)
    

    我们可以单独定义一个setf访问器为

    (defun (setf nth) (n lst)
      ...)
    

    那不是伪代码。defun实际上接受(setf whatever)作为有效的声明函数名称,并且当您setf在函数调用上使用时,宏会查找具有该名称的函数。

    • 1
  2. ad absurdum
    2023-08-23T06:33:43+08:002023-08-23T06:33:43+08:00

    setf接受一个位置和一个值,或多个位置/值对,并将位置的值更改为新值。

    第一个代码示例old-value是绑定在表单内的词法变量let。词法变量是位置,因此调用将设置to(setf old-value new-value)的值。old-valuenew-value

    函数形式也可以是地方,并且nth是一个可以指定地点的函数。在第二个代码示例中,调用(setf (nth 2 lst) new-value)将 place 的值设置(nth 2 lst)为 value new-value。

    因此,在第一个示例中,更新了 place (old-value词法变量),而在第二个示例中,更新了place (nth 2 lst)(列表的元素) 。lst

    有几点需要注意:

    • 第二个示例更新了 的第三个元素,lst因为列表在 Common Lisp 中是零索引的。

    • 由于表单的主体是隐式的,因此不需要progn内部的表单。letletprogn

    • 尝试修改文字会导致 Common Lisp 中出现未定义的行为,因此您不应该使用像这样的带引号列表来调用第二个函数:(to-temp-field '(1 2 3))或使用绑定到列表文字的变量,而是像这样:(to-temp-field (list 1 2 3))或使用绑定到 a 的变量新构建的列表。

    • 1
  3. khachik
    2023-08-23T05:58:30+08:002023-08-23T05:58:30+08:00

    setf简而言之,它是一个宏,它了解正在设置的内容并根据该设置调用相应的集合,因此(setf old-value new value)只需设置该值,而在第二种情况下,它的行为符合预期。

    • 0
  4. Sylwester
    2023-08-23T07:07:49+08:002023-08-23T07:07:49+08:00

    setf是一个宏,当第一个参数是符号时,它与列表结构的某些访问器执行完全不同的操作。

    很容易看出,当您提供符号时,它所做的事情与提供表单时完全不同nth:

    > (get-setf-expansion '(setf (nth 2 lst) new-value))
    (#:TEMP-2859 #:TEMP-2858 #:TEMP-2857) ;
    (2 LST NEW-VALUE) ;
    (#:NEW-2856) ;
    (FUNCALL #'(SETF SYSTEM::%SETNTH) #:NEW-2856 #:TEMP-2859 #:TEMP-2858 #:TEMP-2857) ;
    (SYSTEM::%SETNTH #:TEMP-2859 #:TEMP-2858 #:TEMP-2857)
    
    >  (macroexpand-1 '(setf (nth 2 lst) new-value))
    (SYSTEM::%SETNTH 2 LST NEW-VALUE) ;
    T
    

    最后一行你看到它调用内部函数%setnth。这是特定于实现的,因此 SBCL 确实做了其他事情。然而; 这显然是在获取位置并执行与缓存 from to(rplaca (cddr lst) new-value)值时相同的操作:(nth 2 lst)old-value

    $(get-setf-expansion '(setf old-value 99))
    (#:TEMP-3005 #:TEMP-3004) ;
    (OLD-VALUE 99) ;
    (#:NEW-3003) ;
    (FUNCALL #'(SETF SETQ) #:NEW-3003 #:TEMP-3005 #:TEMP-3004) ;
    (SETQ #:TEMP-3005 #:TEMP-3004)
    
    > (macroexpand-1 '(setf old-value 99))
    (SETQ OLD-VALUE 99) ;
    T
    

    这会执行一个操作setq,因此它只会重新绑定局部变量old-value以指向99。您无法缓存旧位置,因为宏将不再理解它。

    • 0

相关问题

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    使用 <font color="#xxx"> 突出显示 html 中的代码

    • 2 个回答
  • Marko Smith

    为什么在传递 {} 时重载解析更喜欢 std::nullptr_t 而不是类?

    • 1 个回答
  • Marko Smith

    您可以使用花括号初始化列表作为(默认)模板参数吗?

    • 2 个回答
  • Marko Smith

    为什么列表推导式在内部创建一个函数?

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 个回答
  • Marko Smith

    为什么 'char -> int' 是提升,而 'char -> Short' 是转换(但不是提升)?

    • 4 个回答
  • Marko Smith

    为什么库中不调用全局变量的构造函数?

    • 1 个回答
  • Marko Smith

    std::common_reference_with 在元组上的行为不一致。哪个是对的?

    • 1 个回答
  • Marko Smith

    C++17 中 std::byte 只能按位运算?

    • 1 个回答
  • Martin Hope
    fbrereto 为什么在传递 {} 时重载解析更喜欢 std::nullptr_t 而不是类? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 您可以使用花括号初始化列表作为(默认)模板参数吗? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi 为什么列表推导式在内部创建一个函数? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A fmt 格式 %H:%M:%S 不带小数 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python C++20 的 std::views::filter 未正确过滤视图 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute 为什么 'char -> int' 是提升,而 'char -> Short' 是转换(但不是提升)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa 为什么库中不调用全局变量的构造函数? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis std::common_reference_with 在元组上的行为不一致。哪个是对的? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev 为什么编译器在这里错过矢量化? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan C++17 中 std::byte 只能按位运算? 2023-08-17 17:13:58 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve