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



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

我了解如何在单个文件中控制隐私。例如,我可能有一个文件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中,那么我将需要从Bar模块中给它our(即封装)作用域和export作用域,以便能够从Foouse作用域。但这样做的方式与我从Foo导出&public的方式相同,会导致我的库的用户能够访问use Foo并调用&private——这正是我试图避免的结果。如何维护&private的隐私?

(我曾试图通过在META6.json文件中将Foo列为我的分发provides的模块来加强隐私。但从文档中,我的理解是provides控制着像zef这样的包管理器默认安装的模块,但实际上并不控制代码的隐私。这是正确的吗?)

[编辑:我得到的前几个回复让我怀疑我是否遇到了XY问题。我想我问的是"简单的事情应该很容易"类别中的一些问题。我是从Rust背景开始执行API边界的问题,在Rust背景下,通常的做法是在机箱中公开模块(或者只是到他们的父模块)——所以这就是我问的X。但是,如果有更好/不同的方法来强制执行Raku中的API边界,我也会对该解决方案感兴趣(因为这是我真正关心的Y)]

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

第一步没有必要。export机制在词汇范围的sub上也同样有效,这意味着它们只对导入它们的模块可用。由于没有隐式的重新导出,模块用户必须显式地使用包含实现详细信息的模块才能接触到它们。(顺便说一句,就我个人而言,我几乎从不在模块中使用our作用域作为子,而是完全依赖于导出。然而,我明白为什么人们可能会决定以完全限定的名称提供它们。)

还可以使用内部事物的导出标签(is export(:INTERNAL),然后是use My::Module::Internals :INTERNAL),向模块用户提供更强烈的提示,表明他们正在取消保修。最终,无论语言提供什么,有足够决心重用内部的人都会找到一种方法(即使是从模块中复制粘贴)。一般来说,Raku的设计更侧重于让人们更容易做正确的事情,而不是让人们不可能";错误的";如果他们真的想做的话,因为有时错误的事情比其他选择的错误更少。

首先,只要您控制元对象协议,几乎没有什么事情是不能做的。任何语法上可能的东西,原则上都可以使用一种特定类型的方法或类来实现,使用该方法或类声明。例如,您可以有一个private-class,它只对同一命名空间的成员可见(达到您将要设计的级别)。Metamodel::Trusting为特定实体定义了它信任的人(请记住,这是实现的一部分,而不是规范,然后可能会发生更改)。

可伸缩性较差的方法是使用CCD_ 28。新的私有模块需要是类,并为每个访问它的类发出trusts X。这可能包括属于同一分布的类。。。是否,由你自己决定。正是上面的元模型类提供了这种特性,所以直接使用它可能会给你更高级别的控制(使用更低级别的编程)

正如其他人所说,没有办法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,它会变得更加冗长。因此,在大多数情况下,最好将私有函数保存在同一个文件中——但这种选择是在需要时存在的。

最新更新