如何解决在模块分发?级别强制执行 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
,它会变得更加冗长。因此,大多数时候将私有函数保存在同一个文件中可能会更好——但此选项存在于需要时。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。