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

Enforcing API boundaries at the Module (Distribution?) level

我如何构造 Raku 代码,以便某些符号 public 我正在编写的库中,但不 public 给用户图书馆? (我说“库”是为了避免术语“分发”和“模块”,文档有时会以重叠的方式使用它们。但是如果我应该使用更精确的术语,请告诉我。)

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

unit module Foo;

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

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

使用此设置,&public 是我图书馆 public API 的一部分,但 &private 不是——我可以在 Foo 中调用它,但我的用户不能。

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

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

[编辑:我得到的前几个回复让我想知道我是否 运行 进入 XY problem. I thought I was asking about something in the "easy things should be easy" category. I'm coming at the issue of enforcing API boundaries from a Rust background, where the common practice 的东西是制作模块public 在一个板条箱中(或者只是到它们的父模块)——这就是我问的 X。但如果有 better/different 方法在 Raku 中执行 API 边界,我也会对该解决方案感兴趣(因为这是我真正关心的 Y)]

马上,您不能做的事情很少,只要您控制 meta-object protocol. Anything that's syntactically possible, you could in principle do it using a specific kind of method, or class, declared using that. For instance, you could have a private-class which would be visible only to members of the same namespace (to the level that you would design). There's Metamodel::Trusting 定义的特定实体,它信任谁(请记住,这是实现的一部分,而不是规范,然后可能会发生变化)。

一种可扩展性较差的方法是使用 trusts。新的私有模块需要 classes 并为每个 class 访问它的人发出 trusts X。这可能包括 classes 属于同一分布...或不属于,由您决定。正是上面的元模型 class 提供了这个特性,所以直接使用它可能会给你更高级别的控制(编程水平较低)

I will need to give it our (i.e., package) scope and export it from the Bar module

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

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

没有办法像其他人所说的那样 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,它会变得更加冗长。因此,在大多数情况下,将私有函数保存在同一个文件中可能会更好——但此选项只在需要时才存在。