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

Module 在模块(分发?)级别强制执行API边界,module,private,encapsulation,raku,rakudo,Module,Private,Encapsulation,Raku,Rakudo,我如何构造Raku代码,使某些符号在我正在编写的库中是公共的,但对库的用户不是公共的?(我说“库”是为了避免“分发”和“模块”这两个术语,文档有时会以重叠的方式使用它们。但是如果有更准确的术语我应该使用,请告诉我。) 我了解如何在单个文件中控制隐私。例如,我可能有一个包含以下内容的文件Foo.rakumod: unit module Foo; sub private($priv) { #`[do internal stuff] } our sub public($input) is expo

我如何构造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
,那么我需要将
我们的
(即包)范围赋予它,并从
Bar
模块中导出它,以便能够从
Foo
使用
它。但是,以我从
Foo
导出
&public
的相同方式,这样做将导致我的库的用户能够
使用Foo
并调用
&private
——这正是我试图避免的结果。如何维护
&private
的隐私

(我将
Foo
列为我在META6.json文件中的模块,以此来加强隐私。但从文档中,我的理解是
提供了
控制像zef这样的包管理器默认安装的模块,但实际上并不控制代码的隐私。对吗?)


[编辑:我收到的最初几条回复让我怀疑自己是否遇到了类似的问题。我想我是在问“简单的事情应该是简单的”类别中的一些问题。我要问的是从生锈的背景中强制API边界的问题,即在板条箱中公开模块(或者只针对他们的父模块)——这就是我问的X。但是如果有更好的/不同的方法来在Raku中强制执行API边界,我也会对该解决方案感兴趣(因为这是我真正关心的Y)]

一开始,只要你能控制,几乎没有什么你做不到的。任何在语法上可能的事情,原则上你都可以使用一种特定的方法或类来完成。例如,你可以拥有一个只对相同命名空间的成员可见的
私有类(达到您将要设计的级别)。对于特定实体,有一个定义它信任谁(请记住,这是实现的一部分,而不是规范,然后可能会发生更改)

一种可伸缩性较差的方法是使用。新的私有模块需要是类,并为每个访问它的类发出
信任X
。这可能包括属于同一发行版的类…或不属于同一发行版,这取决于您的决定。正是上面的元模型类提供了这种特性,因此直接使用它可能会导致我会让你有更高的控制水平(编程水平更低)

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

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


还可以对内部内容使用导出标记(
是导出(:internal)
,然后
使用My::Module::internal:internal
)向模块用户提供一个更强烈的提示,说明他们正在取消保修。最终,无论使用何种语言,有足够决心重复使用内部组件的人都会找到一种方法(即使是从您的模块复制粘贴)一般来说,Raku的设计更注重让人们更容易做正确的事情,而不是让人们不可能“犯错”如果他们真的想做的话,就做一些事情,因为有时候错误的事情仍然没有其他选择那么错误。

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

但是,您可以使用类似以下的结构:

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

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

当从
Foo
的主线中声明的函数调用时,此函数将正常工作,但如果从其他地方调用,则会引发异常。(当然,用户可以捕获异常;如果您想防止异常,您可以
退出
——但是,有决心的用户可以覆盖
&*退出
处理程序!正如我所说,Raku为用户提供了很大的灵活性)


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

我不相信您会这样做可以。要做的一件事可以是(a)将它们标记为
是实现细节(这里的信号是龙)和(b)仅通过导出向sub提供包密钥,如,
使用Foo::Secret:I-hered-under-that-Foo-Secret-is-designed-for-internal-use-only-and-agree-to-in-hold-
# 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] 
}