在模块分发?级别强制执行 API 边界

问题描述

我如何构建 Raku 代码,以便某些符号在我正在编写的库中公开,但不对库的用户公开? (我说“库”是为了避免使用术语“分发”和“模块”,文档有时会以重叠的方式使用这两个术语。但如果我应该使用更精确的术语,请告诉我。)>

我了解如何在单个文件中控制隐私。例如,我可能有一个包含以下内容的文件 Foo.rakumod

unit module Foo;

sub private($priv) { #`[do internal stuff] }

our sub public($input) is export { #`[ code that calls &private ] }

通过这种设置,&public 是我图书馆公共 API 的一部分,但 &private 不是 - 我可以在 Foo 内调用它,但我的用户不能。

如果 &private 变得足够大以至于我想将其拆分为自己的文件,我该如何保持这种分离?如果我将 &private 移动到 Bar.rakumod,那么我将需要给它 our(即包)范围并从 export 模块中按顺序给它 Bar能够从 use Foo 它。但是,以我从 &public 导出 Foo 的相同方式这样做会导致我图书馆的用户能够use Foo 并调用 &private - 这正是我想要的结果避免。如何维护&private的隐私?

(我通过在 META6.json 文件中将 Foo 列为我的 distribution provides 的模块来研究强制执行隐私。但从文档中,我的理解是 provides 控制哪些模块默认情况下,像 zef install 这样的包管理器实际上并不控制代码的隐私。对吗?)

[编辑:我收到的前几个回复让我怀疑我是否遇到了XY problem。我以为我问的是“简单的事情应该很容易”类别中的某些内容。我正在解决从 Rust 背景强制执行 API 边界的问题,其中 common practice 是在板条箱中公开模块(或仅公开给它们的父模块)——这就是我问的 X。但是,如果有更好/不同的方式来强制 Raku 中的 API 边界,我也会对该解决方案感兴趣(因为这是我真正关心的 Y)]

解决方法

我需要给它我们的(即包)范围并从 Bar 模块导出它

第一步不是必需的。 export 机制也适用于词法范围的 sub,这意味着它们仅可用于导入它们的模块。由于没有隐式重新导出,模块用户必须显式使用包含实现细节的模块才能获得它们。 (顺便说一句,就我个人而言,我几乎从不将 our 范围用于我的模块中的 subs,并且完全依赖于导出。但是,我明白为什么人们也可能决定以完全限定的名称提供它们。)

还可以对内部事物使用导出标签(is export(:INTERNAL),然后是 use My::Module::Internals :INTERNAL),以向模块用户提供更强有力的提示,即他们将失去保修。归根结底,无论语言提供什么,有足够决心重用内部结构的人都会找到一种方法(即使它是从您的模块复制粘贴)。一般来说,Raku 的设计更侧重于让人们更容易做正确的事情,而不是让他们不可能“错误”的事情,如果他们真的想要的话,因为有时错误的事情仍然比其他选择更不错误.

,

首先,只要您控制 meta-object protocol,您不能做的事情就很少。任何在语法上可能的事情,原则上您都可以使用使用它声明的特定类型的方法或类来完成。例如,您可以有一个 private-class,它仅对同一命名空间的成员可见(对于您将设计的级别)。 Metamodel::Trusting 为特定实体定义了它信任的对象(请记住,这是实施的一部分,而不是规范的一部分,然后可能会发生变化)。

可扩展性较差的方法是使用 trusts。新的私有模块需要是类,并为每个访问它的类发出一个 trusts X。这可能包括属于同一发行版的类……与否,这由您决定。正是上面的 Metamodel 类提供了这个特性,所以直接使用它可能会给你更高级别的控制(使用较低级别的编程)

,

正如其他人所说,没有办法 100% 执行此操作。 Raku 只是为用户提供了太多的灵活性,让您能够在外部完美隐藏实现细节,同时仍然在内部文件之间共享它们。

但是,您可以使用类似以下的结构非常接近:

# in Foo.rakumod
use Bar;
unit module Foo;

sub public($input) is export { #`[ code that calls &private ] }
# In Bar.rakumod
unit module Bar;

sub private($priv) is export is implementation-detail {
    unless callframe(1).code.?package.^name eq 'Foo' {
        die '&private is a private function.  Please use the public API in Foo.' }
    #`[do internal stuff] 
}

Foo主线中声明的函数调用该函数时会正常工作,但如果从其他地方调用则会抛出异常。 (当然,用户可以捕获异常;如果您想防止这种情况发生,您可以改为 exit - 但是这样一个确定的用户可以覆盖 &*EXIT 处理程序!正如我所说,Raku 给用户一个很大的灵活性)。

不幸的是,上面的代码有运行时成本并且相当冗长。而且,如果您想从更多位置调用 &private,它会变得更加冗长。因此,大多数时候将私有函数保存在同一个文件中可能会更好——但此选项存在于需要时。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...