Rebol 模块中的单词是如何绑定的? [英] How are words bound within a Rebol module?

查看:9
本文介绍了Rebol 模块中的单词是如何绑定的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道 module! 类型为受保护的命名空间提供了比 object!'use 函数更好的结构.模块中的单词是如何绑定的——我注意到一些与未绑定单词相关的错误:

I understand that the module! type provides a better structure for protected namespaces than object! or the 'use function. How are words bound within the module—I notice some errors related to unbound words:

REBOL [Type: 'module] set 'foo "Bar"

此外,Rebol 如何区分模块本地的单词 ('foo) 和系统函数的单词 ('set)?

Also, how does Rebol distinguish between a word local to the module ('foo) and that of a system function ('set)?

小更新,不久之后:

我看到有一个改变绑定方法的开关:

I see there's a switch that changes the method of binding:

REBOL [Type: 'module Options: [isolate]] set 'foo "Bar"

这有什么不同?默认情况下使用这种方法有什么问题?

What does this do differently? What gotchas are there in using this method by default?

推荐答案

好的,这会有点棘手.

在 Rebol 3 中没有系统词这样的东西,只有词.一些词已经添加到运行时库lib 中,而set 就是这些词之一,它恰好有一个分配给它的函数.模块从 lib 导入单词,尽管导入"的含义取决于模块选项.这可能比您预期的更棘手,所以让我解释一下.

In Rebol 3 there are no such things as system words, there are just words. Some words have been added to the runtime library lib, and set is one of those words, which happens to have a function assigned to it. Modules import words from lib, though what "import" means depends on the module options. That might be more tricky than you were expecting, so let me explain.

对于初学者,我将讨论导入对于常规"模块的含义,即没有指定任何选项的模块.让我们从您的第一个模块开始:

For starters, I'll go over what importing means for "regular" modules, ones that don't have any options specified. Let's start with your first module:

REBOL [Type: 'module] set 'foo "Bar"

首先,你在这里有一个错误的假设:foo这个词不是局部于模块,它与set.如果你想将 foo 定义为一个本地词,你必须使用与对象相同的方法,在顶层使用这个词作为设置词,如下所示:

First of all, you have a wrong assumption here: The word foo is not local to the module, it's just the same as set. If you want to define foo as a local word you have to use the same method as you do with objects, use the word as a set-word at the top level, like this:

REBOL [Type: 'module] foo: "Bar"

fooset 之间的唯一区别是您没有将单词 foo 导出或添加到 lib 还没有.当你在一个模块中引用你没有声明为本地词的词时,它必须从某个地方获取它们的值和/或绑定.对于常规模块,它首先将代码绑定到 lib,然后通过再次将代码绑定到模块的本地上下文来覆盖它.在本地上下文中定义的任何单词都将绑定到它.任何未在本地上下文中定义的词将保留其旧绑定,在本例中为 lib.这就是导入"对于常规模块的意义.

The only difference between foo and set is that you hadn't exported or added the word foo to lib yet. When you reference words in a module that you haven't declared as local words, it has to get their values and/or bindings from somewhere. For regular modules, it binds the code to lib first, then overrides that by binding the code again to the module's local context. Any words defined in the local context will be bound to it. Any words not defined in the local context will retain their old bindings, in this case to lib. That is what "importing" means for regular modules.

在您的第一个示例中,假设您自己没有这样做,那么 foo 一词并未提前添加到运行时库中.这意味着 foo 没有绑定到 lib,并且因为它没有被声明为本地词,所以它也没有绑定到本地上下文.因此,foo 根本没有绑定到任何东西.在您的代码中这是一个错误,但在其他代码中可能不是.

In your first example, assuming that you haven't done so yourself, the word foo was not added to the runtime library ahead of time. That means that foo wasn't bound to lib, and since it wasn't declared as a local word it wasn't bound to the local context either. So as a result, foo wasn't bound to anything at all. In your code that was an error, but in other code it might not be.

有一个隔离"选项可以改变模块导入内容的方式,使其成为隔离"模块.让我们在这里使用您的第二个示例:

There is an "isolate" option that changes the way that modules import stuff, making it an "isolated" module. Let's use your second example here:

REBOL [Type: 'module Options: [isolate]] set 'foo "Bar"

当创建一个独立的模块时,模块中的每个单词,即使是嵌套代码,都会被收集到模块的本地上下文中.在这种情况下,这意味着 setfoo 是本地词.这些单词的初始值设置为它们在创建模块时 lib 中的任何值.也就是说,如果这些词是在 lib 中定义的.如果单词在 lib 中没有值,它们最初也不会在模块中具有值.

When an isolated module is made, every word in the module, even in nested code, is collected into the module's local context. In this case, it means that set and foo are local words. The initial values of those words are set to whatever values they have in lib at the time the module is created. That is, if the words are defined in lib at all. If the words don't have values in lib, they won't initially have values in the module either.

需要注意的是,这种值的导入是一次性的.在初始导入之后,在模块外对这些单词所做的任何更改都不会影响模块中的单词.这就是为什么我们说模块是孤立的".就您的代码示例而言,这意味着有人可以更改 lib/set 并且不会影响您的代码.

It is important to note that this import of values is a one-time thing. After that initial import, any changes to these words made outside the module don't affect the words in the module. That is why we say the module is "isolated". In the case of your code example, it means that someone could change lib/set and it wouldn't affect your code.

但是您错过了另一个重要的模块类型...

But there's another important module type you missed...

在 Rebol 3 中,脚本是另一种模块.这是您的脚本代码:

In Rebol 3, scripts are another kind of module. Here's your code as a script:

REBOL [] set 'foo "Bar"

或者如果你愿意,因为脚本头在 Rebol 3 中是可选的:

Or if you like, since script headers are optional in Rebol 3:

set 'foo "Bar"

脚本也从 lib 导入它们的单词,并将它们导入到一个孤立的上下文中,但有一个转折:所有脚本共享 相同 孤立的上下文,称为用户"上下文.这意味着当您更改脚本中某个单词的值时,使用该单词的 next 脚本将在它启动时看到更改.因此,如果在运行上述脚本后,您尝试运行此脚本:

Scripts also import their words from lib, and they import them into an isolated context, but with a twist: All scripts share the same isolated context, known as the "user" context. This means that when you change the value of a word in a script, the next script to use that word will see the change when it starts. So if after running the above script, you try to run this one:

print foo

然后它会打印Bar",而不是让 foo 未定义,即使 foo 仍然没有在 lib 中定义.如果您以交互方式使用 Rebol 3,在控制台中输入命令并获得结果,那么您可能会发现有趣的是,您输入的每个命令行都是一个单独的脚本.因此,如果您的会话如下所示:

Then it will print "Bar", rather than have foo be undefined, even though foo is still not defined in lib. You might find it interesting to know that if you are using Rebol 3 interactively, entering commands into the console and getting results, that every command line you enter is a separate script. So if your session looks like this:

>> x: 1
== 1
>> print x
1

x: 1print x 行是单独的脚本,第二行利用了第一行对用户上下文所做的更改.

The x: 1 and print x lines are separate scripts, the second taking advantage of the changes made to the user context by the first.

用户上下文实际上应该是任务本地的,但现在让我们忽略这一点.

The user context is actually supposed to be task-local, but for the moment let's ignore that.

这里是我们回到系统功能"的地方,而 Rebol 没有它们.set 函数就像任何其他函数一样.它可能以不同的方式实现,但它仍然是分配给普通单词的普通值.一个应用程序将不得不管理很多的这些词,这就是我们拥有模块和运行时库的原因.

Here is where we get back to the "system function" thing, and that Rebol doesn't have them. The set function is just like any other function. It might be implemented differently, but it's still a normal value assigned to a normal word. An application will have to manage a lot of these words, so that's why we have modules and the runtime library.

在一个应用程序中会有一些需要改变的东西,还有一些需要改变的东西,哪些东西取决于应用程序.您将需要对您的东西进行分组,以保持井井有条或进行访问控制.将有全局定义的内容和本地定义的内容,您将希望有一种有组织的方式将全局内容发送到本地位置,反之亦然,并在不止一个事物想要定义内容时解决任何冲突同名.

In an application there will be stuff that needs to change, and other stuff that needs to not change, and which stuff is which depends on the application. You will want to group your stuff, to keep things organized or for access control. There will be globally defined stuff, and locally defined stuff, and you will want to have an organized way to get the global stuff to the local places, and vice-versa, and resolve any conflicts when more than one thing wants to define stuff with the same name.

在 Rebol 3 中,为了方便和访问控制,我们使用模块对内容进行分组.我们使用运行时库 lib 作为收集模块导出和解决冲突的地方,以控制导入到本地位置的内容,如其他模块和用户上下文.如果您需要覆盖某些内容,您可以通过更改运行时库来实现,并在必要时将您的更改传播到用户上下文.您甚至可以在运行时升级模块,让新版本的模块覆盖旧版本导出的单词.

In Rebol 3, we use modules to group stuff, for convenience and access control. We use the runtime library lib as a place to collect the exports of the modules, and resolve conflicts, in order to control what gets imported to the local places like other modules and the user context(s). If you need to override some stuff, you do this by changing the runtime library, and if necessary propagating your changes out to the user context(s). You can even upgrade modules at runtime, and have the new version of the module override the words exported by the old version.

对于常规模块,当内容被覆盖或升级时,您的模块将受益于此类更改.假设这些变化是有益的,这可能是一件好事.一个常规模块与其他常规模块和脚本合作,形成一个共享的工作环境.

For regular modules, when things are overridden or upgraded, your module will benefit from such changes. Assuming those changes are a benefit, this can be a good thing. A regular module cooperates with other regular modules and scripts to make a shared environment to work in.

但是,有时您需要与这些类型的更改保持距离.也许您需要某个功能的特定版本并且不想升级.也许您的模块将被加载到一个不太可信的环境中,并且您不希望您的代码被黑客入侵.也许你只是需要让事情变得更可预测.在这种情况下,您可能希望将您的模块与这些类型的外部更改隔离开来.

However, sometimes you need to stay separate from these kinds of changes. Perhaps you need a particular version of some function and don't want to be upgraded. Perhaps your module will be loaded in a less trustworthy environment and you don't want your code hacked. Perhaps you just need things to be more predictable. In cases like this, you may want to isolate your module from these kinds of external changes.

被隔离的缺点是,如果您可能想要对运行时库进行更改,您将无法获得它们.如果您的模块以某种方式可访问(例如通过名称导入),则有人可能会将这些更改传播给您,但如果您无法访问,那么您就不走运了.希望您已经考虑过监视 lib 是否有您想要的更改,或者直接通过 lib 引用这些内容.

The downside to being isolated is that, if there are changes to the runtime library that you might want, you're not going to get them. If your module is somehow accessible (such as by having been imported with a name), someone might be able to propagate those changes to you, but if you're not accessible then you're out of luck. Hopefully you've thought to monitor lib for changes you want, or reference the stuff through lib directly.

不过,我们还是错过了另一个重要问题...

Still, we've missed another important issue...

管理运行时库和所有这些本地上下文的另一部分是导出.你必须以某种方式把你的东西拿出来.最重要的因素是您不会怀疑的:您的模块是否有名称.

The other part of managing the runtime library and all of these local contexts is exporting. You have to get your stuff out there somehow. And the most important factor is something that you wouldn't suspect: whether or not your module has a name.

名称对于 Rebol 3 的模块是可选的.起初,这似乎只是一种简化模块编写的方法(在 Carl 的原始提案中,这正是原因).然而,事实证明,有很多事情当你有名字时你可以做而当你没有名字时你不能做,仅仅因为名字是什么:一种指代某物的方式.如果你没有名字,你就没有办法指代某事.

Names are optional for Rebol 3's modules. At first this might seem like just a way to make it simpler to write modules (and in Carl's original proposal, that is exactly why). However, it turns out that there is a lot of stuff that you can do when you have a name that you can't when you don't, simply because of what a name is: a way to refer to something. If you don't have a name, you don't have a way to refer to something.

这似乎是一件微不足道的事情,但这里有一些名字可以让你做的事情:

It might seem like a trivial thing, but here are some things that a name lets you do:

  • 您可以判断模块是否已加载.
  • 您可以确保一个模块只加载一次.
  • 您可以判断较早版本的模块是否存在,并可能对其进行升级.
  • 您可以访问之前加载的模块.

当 Carl 决定将名称设为可选时,他给了我们一种情况,即可以制作您无法做任何这些事情的模块.鉴于模块导出旨在在运行时库中收集和组织,我们遇到了一种情况,您可能会对库产生无法轻易检测到的影响,并且每次导入时都会重新加载模块.

When Carl decided to make names optional, he gave us a situation where it would be possible to make modules for which you couldn't do any of those things. Given that module exports were intended to be collected and organized in the runtime library, we had a situation where you could have effects on the library that you couldn't easily detect, and modules that got reloaded every time they were imported.

因此,为了安全起见,我们决定完全删除运行时库,只将这些未命名模块中的单词直接导出到导入它们的本地(模块或用户)上下文中.这使得这些模块实际上是私有的,就好像它们由目标上下文拥有一样.我们解决了一个潜在的尴尬情况,并把它变成了一个功能.

So for safety we decided to cut out the runtime library completely and just export words from these unnamed modules directly to the local (module or user) contexts that were importing them. This makes these modules effectively private, as if they are owned by the target contexts. We took a potentially awkward situation and made it a feature.

我们决定使用 private 选项明确支持它.将其设为显式选项有助于我们解决最后一个没有名称的问题:使私有模块不必一遍又一遍地重新加载.如果你给一个模块命名,它的导出仍然可以是私有的,但它只需要它导出的内容的一个副本.

It was such a feature that we decided to support it explicitly with a private option. Making this an explicit option helps us deal with the last problem not having a name caused us: making private modules not have to reload over and over again. If you give a module a name, its exports can still be private, but it only needs one copy of what it's exporting.

但是,命名与否,私有与否,这是 3 种导出类型.

However, named or not, private or not, that is 3 export types.

让我们来看看这个模块:

Let's take this module:

REBOL [type: module name: foo] export bar: 1

Importing this 添加一个模块到加载的模块列表中,默认版本为 0.0.0,并导出一个词 bar 到运行时库.在这种情况下,导出"意味着向运行时库添加一个单词 bar(如果它不存在),并将该单词 lib/bar 设置为单词 foo/barfoo 执行完毕后(如果尚未设置).

Importing this adds a module to the loaded modules list, with the default version of 0.0.0, and exports one word bar to the runtime library. "Exporting" in this case means adding a word bar to the runtime library if it isn't there, and setting that word lib/bar to the value that the word foo/bar has after foo has finished executing (if it isn't set already).

值得注意的是,这种自动导出只发生一次,当 foo 的主体完成执行时.如果在此之后对 foo/bar 进行更改,则不会影响 lib/bar.如果你也想改变lib/bar,你必须手动进行.

It is worth noting that this automatic exporting happens only once, when the body of foo is finished executing. If you make a change to foo/bar after that, that doesn't affect lib/bar. If you want to change lib/bar too, you have to do it manually.

另外值得注意的是,如果在导入foo之前lib/bar已经存在,则不会再添加其他单词.如果 lib/bar 已经设置为一个值(未设置),导入 foo 不会覆盖现有值.先到先得.如果要覆盖 lib/bar 的现有值,则必须手动执行此操作.这就是我们如何使用 lib 来管理覆盖.

It is also worth noting that if lib/bar already exists before foo is imported, you won't have another word added. And if lib/bar is already set to a value (not unset), importing foo won't overwrite the existing value. First come, first served. If you want to override an existing value of lib/bar, you'll have to do so manually. This is how we use lib to manage overrides.

运行时库给我们的主要优势是我们可以在一个地方管理所有导出的单词,解决冲突和覆盖.然而,另一个优点是大多数模块和脚本实际上不必说明它们要导入的内容.只要运行时库提前正确填写了您需要的所有单词,您稍后加载的脚本或模块就可以了.这使得在启动代码中放置一堆导入语句和任何覆盖变得容易,这些代码设置了其余代码所需的一切.这是为了更轻松地组织和编写您的应用程序代码.

The main advantages that the runtime library gives us is that we can manage all of our exported words in one place, resolving conflicts and overrides. However, another advantage is that most modules and scripts don't actually have to say what they are importing. As long as the runtime library is filled in properly ahead of time with all the words you need, your script or module that you load later will be fine. This makes it easy to put a bunch of import statements and any overrides in your startup code which sets up everything the rest of your code will need. This is intended to make it easier to organize and write your application code.

在某些情况下,您不想将内容导出到主运行时库.lib 中的东西会被导入到所有东西中,所以你应该只将东西导出到 lib 中,你想让它们普遍可用.有时你想制作只为需要它的上下文导出内容的模块.有时你有一些相关的模块,一个通用工具和一个实用模块等等.如果是这种情况,您可能需要创建一个私有模块.

In some cases, you don't want to export your stuff to the main runtime library. Stuff in lib gets imported into everything, so you should only export stuff to lib that you want to make generally available. Sometimes you want to make modules that only export stuff for the contexts that want it. Sometimes you have some related modules, a general facility and a utility module or so. If this is the case, you might want to make a private module.

让我们来看看这个模块:

Let's take this module:

REBOL [type: module name: foo options: [private]] export bar: 1

导入这个模块不会影响lib.相反,它的导出被收集到一个私有运行时库中,该库是导入该模块的模块或用户上下文的本地,以及目标正在导入的任何其他私有模块的那些,然后从那里导入到目标.私有运行时库用于解决与 lib 相同的冲突.主运行时库 lib 优先于私有库,所以不要指望私有库覆盖全局.

Importing this module doesn't affect lib. Instead, its exports are collected into a private runtime library that is local to the module or user context that is importing this module, along with those of any other private modules that the target is importing, then imported to the target from there. The private runtime library is used for the same conflict resolution that lib is used for. The main runtime library lib takes precedence over the private lib, so don't count on the private lib overriding global things.

这种东西对于制作实用模块、高级 API 或其他此类技巧很有用.它对于制作需要显式导入的强模块化代码也很有用,如果这是您的兴趣的话.

This kind of thing is useful for making utility modules, advanced APIs, or other such tricks. It is also useful for making strong-modular code which requires explicit imports, if that is what you're into.

值得注意的是,如果你的模块实际上没有导出任何东西,命名私有模块和命名公共模块之间没有区别,所以它基本上被视为公共模块.重要的是它有一个名字.这让我们...

It's worth noting that if your module doesn't actually export anything, there is no difference between a named private module or a named public module, so it's basically treated as public. All that matters is that it has a name. Which brings us to...

如上所述,如果您的模块没有名称,那么它几乎必须被视为私有的.不仅是私有的,因为您无法判断它是否已加载,因此您无法升级它,甚至无法重新加载它.但如果这就是你想要的呢?

As explained above, if your module doesn't have a name then it pretty much has to be treated as private. More than private though, since you can't tell if it's loaded, you can't upgrade it or even keep from reloading it. But what if that's what you want?

在某些情况下,您确实希望您的代码运行以产生效果.在这些情况下,您希望每次都重新运行代码.也许它是一个脚本,您使用 do 运行但将其构建为模块以避免泄漏单词.也许你正在制作一个 mixin,一些具有一些需要初始化的本地状态的实用程序函数.它可以是任何东西.

In some cases, you really want your code run for effect. In these cases having your code rerun every time is what you want to do. Maybe it's a script that you're running with do but structuring as a module to avoid leaking words. Maybe you're making a mixin, some utility functions that have some local state that needs initializing. It could be just about anything.

我经常将我的 %rebol.r 文件设为未命名模块,因为我想对其导出的内容和方式有更多的控制.另外,由于它是为了效果而做的,不需要重新加载或升级,所以给它命名是没有意义的.

I frequently make my %rebol.r file an unnamed module because I want to have more control over what it exports and how. Plus, since it's done for effect and doesn't need to be reloaded or upgraded there's no point in giving it a name.

不需要代码示例,您之前的代码会以这种方式运行.

No need for a code example, your earlier ones will act this way.

我希望这能让您对 R3 的模块系统的设计有足够的了解.

这篇关于Rebol 模块中的单词是如何绑定的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆