rebol - Red/Rebol 的函数如何通过值或引用传递参数?
问题描述
我对以下两个代码的结果感到困惑:
代码1:
>> f: func [x][head insert x 1]
== func [x][head insert x 1]
>> a: [2 3]
== [2 3]
>> f a
== [1 2 3]
>> a
== [1 2 3] ;; variable a is destroyed
代码2:
>> g: func [x y][x: x + y]
== func [x y][x: x + y]
>> c: 1 d: 2
== 2
>> g c d
== 3
>> c
== 1
>> d
== 2
;;variable c and d keep their original values
我的问题是:Red/Rebol 中的函数如何通过值或引用获取参数?
解决方案
这是个好问题。答案是:参数是按值传递的,但有些参数中可能包含引用。
Rebol/Red 中的每个值都表示为大小一致的盒装结构,通常称为slot或cell。该结构在逻辑上分为 3 部分:header、payload 和 extra。
┌─────────┐
│ header │
├─────────┤ Historically, the size of the value slot is 4 machine pointers in size,
│ payload │ 1 for header, 1 for extra, and 2 for payload.
│ │ E.g. on 32-bit systems that's 128 bits, or 16 bytes.
├─────────┤
│ extra │
└─────────┘
- 标头包含各种元信息,有助于识别有效负载包含的值,最重要的部分是类型标记,或者用 Rebol 的话来说,是数据类型 ID。
- 有效载荷包含一些值的数据表示,例如数字、字符串、字符等。
- 额外的部分用作为优化(例如缓存)和存储不适合有效负载的数据保留的空间。
现在,值槽具有统一的大小,自然,一些数据根本无法完全放入其中。为了解决这个问题,值槽可以包含对外部缓冲区的引用(基本上是一个带有额外间接层的指针,以使数据可垃圾收集并在多个槽之间共享)。
┌─────────┐
│ header │
├─────────┤
│ payload │ --→ [buffer]
│ │
├─────────┤
│ extra │
└─────────┘
适合值槽的值(例如scalar!
)称为direct,不适合的值(例如series!
)称为间接:因为其中的引用在值槽和实际数据之间引入了间接级别。例如,这里是在 Red 中定义各种插槽布局的方式。
value slot 的内容只是一堆字节;运行时如何解释它们取决于标头中的数据类型 ID。一些字节可能只是文字,而另一些可能是指向数据缓冲区的间接指针。将参数传递给函数只是复制这些字节,不管它们是什么意思。因此,在这方面,文字和引用都被视为相同。
所以,如果你有一个内部看起来像这样的值槽:
┌────────┐
│DEADBEEF│ header
├────────┤
│00000000│ payload
│FACEFEED│
├────────┤
│CAFEBABE│ extra
└────────┘
然后,比如说,FACEFEED
可以是一个有符号整数-87097619
,或者打包在一起的不同大小的位域,或者它可以是一个机器指针:这取决于标头(例如EF
字节)中的数据类型 ID 归属于它。
当值槽作为参数传递给函数时,它的所有字节都将简单地复制到评估堆栈上,而不管它们编码或表示什么。对于直接值,逻辑很简单:如果在函数中修改了参数,则原始值保持不变,因为它只是一个副本。这就是您的第二个示例的全部内容。
Parameter Stack
┌────────┐ ┌────────┐
│DEADBEEF│ │DEADBEEF│
├────────┤ ├────────┤
│00000000│ │00000000│ Both represent the same integer -87097619.
│FACEFEED│ │FACEFEED│ ← You modify this one, with no effect on the other.
├────────┤ ├────────┤
│CAFEBABE│ │CAFEBABE│
└────────┘ └────────┘
但是使用间接值会更有趣。它们也被逐字复制,但这使得两个副本共享对单个缓冲区的相同引用(请记住,表示引用的字节在两个插槽中是相同的)。因此,当您通过一个(例如insert
头部的元素)修改缓冲区时,另一个也反映了更改。
Parameter Stack
┌────────┐ ┌────────┐
│DEADBEEF│ │DEADBEEF│
├────────┤ ├────────┤
│00000000│ │00000000│ Both refer to the same buffer (same machine pointers!)
│FACEFEED│──┐───│FACEFEED│
├────────┤ │ ├────────┤
│CAFEBABE│ │ │CAFEBABE│
└────────┘ │ └────────┘
↓
[b u f f e r] ← You modify the content of the buffer.
回到你的第一个例子:
>> f: func [x][head insert x 1]
== func [x][head insert x 1]
>> a: [2 3]
== [2 3]
>> f a
== [1 2 3]
>> a
== [1 2 3] ;; variable a is destroyed
简化了很多,这就是引擎盖下的样子:
value slot buffer value slot (parameter on stack)
<word a in global context> --→ [1 2 3] ←-- <word x in function's context>
自然地,有一些方法可以克隆值槽和它所引用的缓冲区:这就是这样copy
做的。
>> f: func [x][head insert x 1]
== func [x][head insert x 1]
>> a: [2 3]
== [2 3]
>> f copy a
== [1 2 3]
>> a
== [2 3]
以图解方式(再次,相当简化):
value slot buffer
<x> --→ [1 2 3]
<a> --→ [2 3]
系列值(例如块)在其有效负载中还包含另一条数据:索引。
>> at [a b c d] 3 ; index 3, buffer → [a b c d]
== [c d]
当将块作为参数传递时,它的索引也会被复制过来,但与数据缓冲区不同的是,它不会在两个值槽之间共享。
Parameter Stack
┌────────┐ ┌────────┐
│DEADBEEF│ │DEADBEEF│
├────────┤ ├────────┤
│00000000│ │00000000│ Suppose that 0's here are an index.
│FACEFEED│──┐───│FACEFEED│ Modifying this one won't affect the other.
├────────┤ │ ├────────┤
│CAFEBABE│ │ │CAFEBABE│
└────────┘ │ └────────┘
↓
[b u f f e r]
所以:
>> foo: func [x][x: tail x] ; tail puts index, well, at the tail
== func [x][x: tail x]
>> y: [a b c]
== [a b c]
>> foo y
== [] ; x is modified
>> y
== [a b c] ; y stays the same
推荐阅读
- c# - 使用 ContinueDialogAsync 时字典中不存在给定键“对话框”
- c# - 有条件地创建对象
- delphi - 表单控件(编辑、组合框、备忘录等)查询是否已修改?
- sql - Firebird/Lazarus SQL 视图与选择 iif?
- java - 如何在 JavaFX 应用程序中加载的 FXML 文件中显示元素?
- elasticsearch - 配置 Grafana 数据源以使用多个 ElasticSearch 节点
- javascript - 如何堆叠精灵元素而不通过对象丢弃
- curl - 卷曲请求 - 无法解析主机:“密码”
- c# - 'await' 运算符只能在异步 lambda 表达式中使用。考虑用 'async' 修饰符标记这个 lambda 表达式
- sql - Impala - 在 WITH 子句之后创建 TABLE