Letrec 和可重入延续

2024-05-02

有人告诉我,以下表达式的计算结果为 0,但许多方案的实现将其计算为 1:

(let ((cont #f))
  (letrec ((x (call-with-current-continuation (lambda (c) (set! cont c) 0)))
           (y (call-with-current-continuation (lambda (c) (set! cont c) 0))))
    (if cont
        (let ((c cont))
          (set! cont #f)
          (set! x 1)
          (set! y 1)
          (c 0))
        (+ x y))))

我必须承认,我什至不知道从哪里开始。我了解延续的基础知识和call/cc,但是我能得到这个表达式的详细解释吗?


这是一个有趣的片段。我遇到这个问题是因为我正在寻找关于两者之间确切差异的讨论letrec and letrec*,以及不同版本的计划报告和不同的计划实现之间的差异。在尝试这个片段时,我做了一些研究,并将在这里报告结果。

如果您在心里回顾一下该片段的执行过程,您应该会注意到两个问题:

Q1.初始化子句的顺序是什么x and y评价?

Q2。是否首先评估所有初始化子句,并缓存其结果,然后再进行所有分配x and y之后执行?或者某些赋值是在某些初始化子句求值之前进行的吗?

For letrec,Scheme 报告称 Q1 的答案是“未指定”。事实上,大多数实现都会按从左到右的顺序评估子句;但你不应该依赖这种行为。

方案R6RS和R7RS引入了新的绑定结构letrec*它确实指定了从左到右的评估顺序。它在其他一些方面也与letrec,正如我们将在下面看到的。

回到letrec,该计划报告至少可以追溯到 R5RSseem指定 Q2 的答案是“在进行任何分配之前评估所有初始化子句”。我说“似乎指定”是因为语言对于这一点的要求并不像它可能的那样明确。事实上,很多Scheme的实现并不符合这个要求。这就是造成片段的“预期”和“观察到”行为之间差异的原因。

让我们回顾一下您的片段,并考虑到 Q2。首先,我们留出两个“位置”(参考单元格)x and y被束缚。然后我们评估初始化子句之一。假设这是一个子句x,尽管正如我所说,与letrec可能是其中之一。我们将这个评估的延续保存到cont。这个评估的结果是 0。现在,根据 Q2 的答案,我们要么立即将该结果分配给x或者我们缓存它以便稍后进行分配。接下来我们评估其他初始化子句。我们将其延续保存到cont,覆盖前一个。此评估的结果是 0。现在所有初始化子句都已评估。根据 Q2 的答案,我们此时可以将缓存结果 0 分配给x;或分配给x可能已经发生了。在任何一种情况下,分配给y现在发生。

然后我们开始评估主体(letrec (...) ...)表达(第一次)。有一个延续存储在cont,所以我们将其检索到c,然后清除cont and set!每一个x and y到 1。然后我们用值 0 调用检索到的延续。这会回到最后评估的初始化子句——我们假设它是y'是。然后使用我们提供给延续的参数来代替(call-with-current-continuation (lambda (c) (set! cont c) 0)),并将被分配给y。根据 Q2 的答案,将 0 分配给x此时可能会或可能不会(再次)发生。

然后我们开始评估主体(letrec (...) ...)表达(第二次)。现在cont是#f,所以我们得到(+ x y)。哪个将是(+ 1 0) or (+ 0 0),取决于 0 是否被重新分配给x当我们调用保存的延续时。

您可以通过用一些装饰您的片段来跟踪此行为display调用,例如这样:

(let ((cont #f))
 (letrec ((x (begin (display (list 'xinit x y cont)) (call-with-current-continuation (lambda (c) (set! cont c) 0))))
          (y (begin (display (list 'yinit x y cont)) (call-with-current-continuation (lambda (c) (set! cont c) 0)))))
  (display (list 'body x y cont))
  (if cont
   (let ((c cont))
    (set! cont #f)
    (set! x 1)
    (set! y 1)
    (c 'new))
   (cons x y))))

我也更换了(+ x y) with (cons x y),并用参数调用延续'new代替0.

我在 Racket 5.2 中使用几种不同的“语言模式”运行了该片段,也在 Chicken 4.7 中运行了该片段。这是结果。两种实现都评估了x首先是 init 子句,然后y第二条,尽管正如我所说,这种行为是未指定的。

球拍配#lang r5rs and #lang r6rs符合 Q2 的规范,因此我们得到了重新分配的“预期”结果0当调用延续时到另一个变量。 (在尝试 r6rs 时,我需要将最终结果包装在display看看会是什么。)

这是跟踪输出:

(xinit #<undefined> #<undefined> #f)
(yinit #<undefined> #<undefined> #<continuation>)
(body 0 0 #<continuation>)
(body 0 new #f)
(0 . new)

球拍配#lang racket而鸡不符合这一点。相反,在评估每个初始化子句后,它会被分配给相应的变量。因此,当调用延续时,它最终只会将一个值重新分配给最终值。

这是跟踪输出,并添加了一些注释:

(xinit #<undefined> #<undefined> #f)
(yinit 0 #<undefined> #<continuation>) ; note that x has already been assigned
(body 0 0 #<continuation>)
(body 1 new #f) ; so now x is not re-assigned
(1 . new)

现在,至于计划报告真正需要什么。以下是 R5RS 的相关部分:

库语法:(letrec )

语法: 应采用以下形式 (( ) ...), 应该是一个或多个表达式的序列。这是一个错误 使 在绑定的变量列表中出现多次。

语义: 绑定到未定义的新位置 值, 在结果环境中进行评估(在某些情况下) 未指定的顺序),每个 被分配给 对应的 , 在结果环境中进行评估,并且 返回 中最后一个表达式的值。每个绑定 a 将整个 letrec 表达式作为其区域,使其成为可能 定义相互递归的过程。

(letrec ((even?
          (lambda (n)
            (if (zero? n)
                #t
                (odd? (- n 1)))))
         (odd?
          (lambda (n)
            (if (zero? n)
                #f
                (even? (- n 1))))))
  (even? 88))   
===>  #t

对 letrec 的一个限制非常重要:它必须能够评估 每个 不分配或引用值any。如果 违反了这个限制,那么它就是一个错误。限制是必要的 因为Scheme按值而不是按名称传递参数。在最 letrec 的常见用法,所有 都是 lambda 表达式,并且 自动满足限制。

“语义”部分的第一句话听起来像是需要完成所有作业after所有初始化子句都已被评估;不过,正如我之前所说,这并不像想象的那么明确。

在 R6RS 和 R7RS 中,规范这部分的唯一实质性更改是添加了以下要求:

每个 的后续部分不应被调用多次。

R6RS和R7RS还添加了another不过,绑定结构:letrec*。这不同于letrec有两种方式。首先,它指定了从左到右的评估顺序。相应地,上述“限制”可以适当放宽。现在可以引用已分配初始值的变量值:

必须能够在不分配或 指的是值相应的 中紧随其后的任何绑定的 .

第二个区别是关于我们的第二季度。和letrec*,规范现在要求在评估每个初始化子句之后进行分配。这是 R7RS(草案 6)中“语义”的第一段:

语义: 绑定到新位置,每个 按从左到右的顺序分配给评估结果 对应的, 在结果中进行评估 环境, 中最后一个表达式的值为 回。尽管评估和分配顺序是从左到右,但每个 的绑定将整个 letrec* 表达式作为其区域, 使得定义相互递归过程成为可能。

所以鸡和球拍使用#lang racket---以及许多其他实现---实际上似乎实现了它们的letrecs as letrec*s.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Letrec 和可重入延续 的相关文章

  • 有人可以向我解释“卫生”的概念吗(我是一名方案程序员)?

    所以 我是 r6rs 方案的新手 正在学习宏 谁能给我解释一下 卫生 是什么意思 提前致谢 卫生经常在宏的上下文中使用 卫生宏不使用可能会干扰正在扩展的代码的变量名称 这是一个例子 假设我们想要定义or带有宏的特殊形式 直观地说 or a
  • 对于方案中的每个和地图

    这两个功能在方案上有什么区别吗 我正在使用 Dr Racket R5RS 语言制作一个模拟器游戏 我无法决定哪个更好 for each从左到右计算列表元素上的给定函数 并丢弃函数的返回值 它非常适合对列表中的每个元素进行副作用操作 map以
  • 协程、延续、生成器

    协程 延续和生成器有什么区别 我将从生成器开始 因为它们是最简单的情况 正如 zvolkov 提到的 它们是可以重复调用而不返回的函数 对象 但在调用时将返回 产生 一个值 然后挂起它们的执行 当他们再次被调用时 他们将从上次暂停执行的地方
  • 内联定义函数与非内联函数有什么区别?

    我正在读这本书计算机程序的结构和实现 http mitpress mit edu sicp full text book book Z H 4 html在其中一章中 有一些代码用于计算数字的阶乘 define factorial n fac
  • 用 Haskell 解释 Parigot 的 lambda-mu 演算

    我们可以用 Haskell 来解释 lambda 演算 data Expr Var String Lam String Expr App Expr Expr data Value a V a F Value a gt Value a int
  • 有没有办法像 withCString 一样链接函数?

    有没有办法链接像这样的函数withCString 我的意思是任何 函数看起来像f Foo gt CFoo gt IO a gt IO a 例如 假设有一个函数cFunc CString gt CFoo gt CBar gt IO 通常 我会
  • schema 中的方法和属性:Scheme 中是否可以实现 OOP?

    我将用一个简单的例子来说明我的问题 在 Java C 或任何其他 OOP 语言中 我可以创建一个pie类的方式类似于 class Apple public String flavor public int pieces private in
  • 将函数列表应用于数字

    据我了解 Scheme Racket 中的函数 如 map foldr 和 filter 可以做一些奇妙的事情 例如将函数应用于元素列表 是否可以将函数列表应用于单个元素 我想生成每个函数产生的值 然后找到它们的最大值 谢谢 对于第一部分
  • 方案作业

    当我每次得到值 10 时评估以下表达式 lambda x lambda set x x 10 x 0 不过 我只是通过用名称抽象上述过程来进行修改 并在每次值增加 10 时调用 foo define foo lambda x lambda
  • 将字符串附加到 IronScheme 中的现有文本文件

    我们正在尝试使用 IronScheme 构建一个日志文件 并且我们已经使用racket 为其编写了代码 它在球拍中工作正常 但 IronScheme 会抛出错误 这是我们目前所拥有的 define write to log lambda w
  • 方案中的延续传递风格?

    我遇到了这段代码在维基百科上 http en wikipedia org wiki Continuation passing style define pyth x y k x x lambda x2 y y lambda y2 x2 y2
  • 按方案中的第一个元素对列表列表进行排序

    例如 我正在研究按第一个元素对列表列表进行排序 排序 列表 2 1 6 7 4 3 1 2 4 5 1 1 预期输出 gt 1 1 2 1 6 7 4 3 1 2 4 5 我使用的算法是冒泡排序 我修改了它来处理列表 但是 该代码无法编译
  • 方案按引用传递

    如何在方案中通过引用传递变量 我想要的功能的示例 define foo lambda x set x 5 define y 2 foo y display y outputs 5 另外 有没有办法通过引用返回 See http commun
  • 如何找到 MIT 方案中出现错误的地方?

    当你在 MIT 方案中遇到错误时 它不会告诉你错误发生在哪里 例如 它只打印如下内容 Unbound variable top left To continue call RESTART with an option number REST
  • (Chez) 用于隐藏 lambda 的方案宏

    我想编写一个宏来创建速记语法来隐藏更详细的 lambda 表达式 但我很难理解如何编写宏 我意识到这是反对使用它们的一个论据 给出这个例子 define alist example x 1 2 3 y 4 5 6 z 7 8 9 defin
  • 传递给过程的列表转换为过程内列表的列表

    我正在 DrRacket 上调试这段代码 lang racket define last element on list lambda l cond null l null cdr l car l else last element on
  • 方案语言:合并两个数字

    如何将列表中的两个整数合并为一个 方案中 例子 11 223 gt 11223 假设列表恰好有两个元素 并且都是数字 define merge numbers lst let 1st number gt string first lst 2
  • 方案如何返回多个值?

    我注意到几乎所有方案函数只能返回一个列表作为输出 下面 我想返回邻居的所有相邻节点的多个值 define neighbors l w if and 1 l 1 w list and l 1 w and 1 l w how to output
  • 为什么Racket中foldl的定义方式很奇怪?

    在 Haskell 中 与许多其他函数式语言一样 函数foldl被定义为 例如 foldl 0 1 2 3 4 10 这没关系 因为foldl 0 1 2 3 4 根据定义 0 1 2 3 4 但是 在 球拍 中 foldl 0 1 2 3
  • 我在函数的最后一次递归调用中得到“方案应用程序而不是过程”

    所以这是代码 define time prime test n newline display n start prime test n runtime define start prime test n start time if pri

随机推荐

  • System.ArgumentException:程序集中的重复类型名称

    我正在使用 EF 4 1 开发 ASP Net MVC 3 Web 应用程序 从今天开始 我收到此错误 System ArgumentException 程序集中的重复类型名称 我不知道是什么原因造成的 执行查找时 它发生在我的存储库中 p
  • 适用于 iPhone 的 JavaScript 可从非默认 iOS 浏览器在 Safari 中打开

    在移动 Safari 中打开的 googlechrome www lego com 将切换到 Google Chrome iOS 应用程序来打开该 URL 这允许像下面这样的 scriptlet 它允许您从移动 Safari 切换到 Goo
  • Summernote onKeyup 事件未按预期工作

    我将 Summernote 编辑器应用于文本区域 我希望当我在编辑器中键入一些文本时 该文本应反映在 div 中 因此我有一个文本区域和一个 div result 其中应在每次按键时写入更改事件
  • 计算回头客

    我正在分析一家商店的销售数据 并希望计算 第一订单客户 在下个月变成回头客的百分比 我有一个包含所有订单的数据框 其中包括客户 ID 日期和标记 如果这是他 她的第一笔订单 这是我的数据 import pandas as pd data N
  • 运行时之前初始化的数据段值将存储在哪里?

    通常数据段在C code位于RAM易失性存储器 由初始化数据段组成 未初始化数据段 BSS 堆栈内存和堆 堆栈内存仅在运行时调用例程和在push and pull的价值观 堆用于动态内存分配调用malloc calloc and reall
  • 我在 Python 中查找重复循环的正则表达式模式有什么问题?

    我想匹配任何具有重复循环的字符串 就像这个数据一样 3333333333333333333333333333333333333333 1 digit cycle 3 1666666666666666666666666666666666666
  • React 18 的 create-react-app 依赖版本问题

    npx create react app my project导致以下依赖错误 npx版本 8 5 0 Installing template dependencies using npm npm ERR code ERESOLVE npm
  • 通过 Facebook iOS SDK 获取我的所有活动

    在我的 iOS 应用程序中 我使用以下代码获取访问令牌 self facebook authorize NSArray arrayWithObjects user events friends events nil 然后我使用以下代码请求我
  • java堆空间OutOfMemoryError分析工具

    我正在得到一个OutOfMemoryError Java heap space 我可以使用任何工具来查找根本原因吗 您可以使用一些分析工具 例如 eclipse mat 分析应用程序的堆转储 以查看哪些内容消耗了多少堆 但首先您需要获取应用
  • 如何选择不同级别的多个节点?

    拥有这个 简化的 XML
  • Visual Studio 2017 无法安装多个组件

    Visual Studio 2017 社区版发行版的安装程序因多个组件而失败 由于以下原因 产品无法安装列出的工作负载和组件 一个或多个包失败 工作负载不完整 使用 NET进行移动开发 Microsoft VisualStudio Work
  • C++:错误:限定名称的使用无效

    更新 我认为它已经修复了 多谢你们 我收到错误 但我无法弄清楚 我有这个代码 A Structure with one variable and a constructor struct Structure public int dataM
  • 设置背景时按钮变大 - 如何使其变小

    我想让我的按钮在设置背景之前缩小或恢复正常 我知道使用背景色调可以使用相同的背景颜色来解决此问题 但我的问题是我在背景上使用选择器 当选择器设置为按钮背景时 它变得更宽 当我将背景切换为背景色调时 颜色变得不同 例如对我来说是紫色 并且按下
  • 如何在 ActiveAdmin 中正确配置 Rails 4.1 枚举

    我有一个 Rails 4 1 应用程序 其中使用枚举来表示对象的隐私级别 在我的架构中 t integer privacy level default 0 在我的模型中 enum privacy level privacy private
  • 从 SQL 表在 SQL 中创建数据透视视图

    我有下表TEMP 我想使用 SQL 创建一个数据透视视图 排序依据CATEGORYASC 通过LEVEL降序和SETASC 并填写value 预期输出 我已尝试以下代码 但无法解决引发错误的聚合部分 SELECT FROM SELECT S
  • 自动化 Windows UI 测试方法

    我们正在寻求设置自动化 UI 测试 并想知道最好的方法是什么 潜在的陷阱是什么 设置费用是否昂贵 提前致谢 B 自动化测试最大的消耗可能是时间 有很多非常昂贵的工具 但也有免费的工具 即使是昂贵的工具的成本也不太可能与正确设置自动化测试所需
  • 如何处理Spring Boot中的SQLIntegrityConstraintViolationException?

    我正在 RestController 工作 尝试使用 try catch 块处理 SQLIntegrityConstraintViolationException 但我不知道为什么 catch 块从未执行 这是休息控制器 import ja
  • 使用 socket.io node.js 和传入消息的通知系统的架构实现和设计

    免责声明 我之前没有使用过node js 我以前没有使用过socket io 我正在考虑实现 Google Plus Facebook StackOverflow 风格的通知系统 我不是一个没有经验的开发人员 最终我会解决这个问题 但我只是
  • 增加android中的网格间距

    我有一个网格视图 其中三列中有很多项目 我想增加它们之间的间距 我怎样才能在安卓中做到这一点 您可以使用android verticalSpacing and android horizontalSpacing在 GridView 标记中并
  • Letrec 和可重入延续

    有人告诉我 以下表达式的计算结果为 0 但许多方案的实现将其计算为 1 let cont f letrec x call with current continuation lambda c set cont c 0 y call with