如何在Rebol模块中绑定单词?

rgc*_*ris 19 namespaces module rebol code-organization rebol3

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

REBOL [Type: 'module] set 'foo "Bar"
Run Code Online (Sandbox Code Playgroud)

另外,Rebol如何区分module('foo)的本地字和系统函数('set)的字?

稍后更新,不久之后:

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

REBOL [Type: 'module Options: [isolate]] set 'foo "Bar"
Run Code Online (Sandbox Code Playgroud)

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

Bri*_*anH 21

好的,这有点棘手.

在Rebol 3中没有系统词这样的东西,只有单词.有些单词已添加到运行时库中lib,并且set是其中一个单词,恰好分配了一个函数.模块导入单词lib,但"导入"的含义取决于模块选项.这可能比你期望的更棘手,所以让我解释一下.

常规模块

对于初学者,我将介绍"常规"模块的导入方法,这些模块没有指定任何选项.让我们从你的第一个模块开始:

REBOL [Type: 'module] set 'foo "Bar"
Run Code Online (Sandbox Code Playgroud)

首先,你有一个错误的假设这里:这个词foo不是本地的模块,它只是一样set.如果要定义foo为本地单词,则必须使用与对象相同的方法,在顶层使用单词作为set-word,如下所示:

REBOL [Type: 'module] foo: "Bar"
Run Code Online (Sandbox Code Playgroud)

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

在您的第一个示例中,假设您自己没有这样做,这个词foo没有提前添加到运行时库中.这意味着foo没有约束lib,因为它没有被声明为本地词,所以它也没有绑定到本地语境.因此,foo完全没有任何约束力.在您的代码中出现错误,但在其他代码中可能不是.

孤立的模块

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

REBOL [Type: 'module Options: [isolate]] set 'foo "Bar"
Run Code Online (Sandbox Code Playgroud)

制作隔离模块时,模块中的每个单词(即使在嵌套代码中)都会收集到模块的本地上下文中.在这种情况下,它意味着set并且foo是本地词.这些单词的初始值设置lib为创建模块时的任何值.也就是说,如果完全定义了单词lib.如果单词没有值lib,则它们最初也不会在模块中具有值.

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

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

脚本

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

REBOL [] set 'foo "Bar"
Run Code Online (Sandbox Code Playgroud)

或者如果您愿意,因为Rebol 3中的脚本标题是可选的:

set 'foo "Bar"
Run Code Online (Sandbox Code Playgroud)

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

print foo
Run Code Online (Sandbox Code Playgroud)

然后它将打印"Bar",而不是foo未定义,即使foo仍未定义lib.您可能会感到有趣的是,如果您以交互方式使用Rebol 3,将命令输入控制台并获取结果,则您输入的每个命令行都是一个单独的脚本.所以如果你的会话看起来像这样:

>> x: 1
== 1
>> print x
1
Run Code Online (Sandbox Code Playgroud)

x: 1print x线是独立的脚本,由第一至用户上下文进行的更改第二趁势.

用户上下文实际上应该是任务本地的,但是暂时让我们忽略它.

为什么不同?

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

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

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

对于常规模块,当事物被覆盖或升级时,您的模块将从这些更改中受益.假设这些改变是有益的,这可能是一件好事.常规模块与其他常规模块和脚本协作以使共享环境工作.

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

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

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

出口

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

对于Rebol 3的模块,名称是可选的.起初,这似乎只是一种简化编写模块的方法(而在Carl的原始提案中,这正是原因).然而,事实证明,当你有一个你不能做到的名字时,你可以做很多事情,仅仅是因为名字是什么:一种引用某种东西的方式.如果您没有姓名,则无法引用某些内容.

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

  • 您可以判断模块是否已加载.
  • 您可以确保模块仅加载一次.
  • 您可以先判断一个旧版本的模块是否存在,也可以升级它.
  • 您可以访问先前加载的模块.

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

因此,为了安全起见,我们决定完全删除运行时库,并将这些未命名模块中的单词直接导出到导入它们的本地(模块或用户)上下文.这使得这些模块实际上是私有的,就好像它们归目标上下文所有.我们采取了一个潜在的尴尬局面,并将其作为一项功能.

这是一个功能,我们决定明确支持它的private选项.使这个显式选项有助于我们处理最后一个没有名称导致我们的问题:使私有模块不必一次又一次地重新加载.如果为模块命名,其导出仍然可以是私有的,但它只需要导出一个副本.

但是,无论是否为私有,私有与否,即3种导出类型.

常规命名模块

我们来看看这个模块:

REBOL [type: module name: foo] export bar: 1
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

命名为私有模块

在某些情况下,您不希望将您的东西导出到主运行时库.东西lib被导入到所有东西中,所以你应该只将东西导出到lib你想要普遍可用的东西.有时你想制作只为那些想要它的上下文导出东西的模块.有时你有一些相关的模块,一般设施和一个实用模块左右.如果是这种情况,您可能想要创建一个私有模块.

我们来看看这个模块:

REBOL [type: module name: foo options: [private]] export bar: 1
Run Code Online (Sandbox Code Playgroud)

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

这种事情对于制作实用程序模块,高级API或其他此类技巧很有用.它对于制作需要显式导入的强模块化代码也很有用,如果这是你所要做的.

值得注意的是,如果您的模块实际上没有导出任何内容,则命名的私有模块或命名的公共模块之间没有区别,因此它基本上被视为公共模块.重要的是它有一个名字.这让我们...

未命名的模块

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

在某些情况下,您确实希望代码运行起作用.在这些情况下,每次重新运行代码都是您想要执行的操作.也许这是一个你正在运行的脚本,do但结构化为一个模块,以避免泄漏单词.也许你正在制作一个mixin,一些实用函数有一些需要初始化的本地状态.它可能是任何事情.

我经常使我的%rebol.r文件成为一个未命名的模块,因为我希望能够更好地控制它的导出内容和方式.此外,由于它已经完成效果而且不需要重新加载或升级,所以没有必要给它起一个名字.

不需要代码示例,您之前的代码将采取这种方式.

我希望这能为您提供有关R3模块系统设计的概述.