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

     2023-02-25     292

关键词:

【中文标题】在模块(分发?)级别强制执行 API 边界【英文标题】:Enforcing API boundaries at the Module (Distribution?) level 【发布时间】:2021-06-13 08:17:12 【问题描述】:

我如何构建 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 模块给它our(即包)范围和export,以便能够从@use 它987654336@。但是这样做与我从Foo 导出&public 的方式相同,将导致我的库的用户能够use Foo 并调用&private——这正是我试图避免的结果。如何维护&private的隐私?

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

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

【问题讨论】:

我不相信你可以。要做的一件事可能是(a)将它们标记为is implementation-detail(这表明这里是龙)并且(b)仅通过导出提供带有包密钥的子,例如use Foo::Secret :I-hereby-understand-that-foo-secret-is-designed-for-internal-use-only-and-agree-to-in-hold-the-module-author-harmless-for-any-and-all-damages-thereby-caused-in-sæcula-sæculorum ,或者类似的巧妙写法(你毕竟是律师) 另一种选择可能是将 EVAL 从资源文件转换为 my 范围值,但我在将代码块存储在预编译文件中时遇到了问题,因此您可能必须在运行时完成,失去你无疑想要的好处(但也许 jnhtn 的新调度东西会产生修复它的副作用) 【参考方案1】:

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

【讨论】:

“正如其他人所说,没有办法强制执行此 100%。Raku 只是为用户提供了太多的灵活性”该摘要似乎具有极大的误导性。你可以说 any PL 一样的话。 Haskell 模块将受到完全相同的考虑;正如 jnthn 所指出的——作为一个关键点,而不是一个丢弃点——人们总是可以剪切/粘贴代码。那么PL能做什么呢? Raku 与你所说的完全相反。它提供了图灵完备 GPL 的灵活性,也就是说完全的灵活性,这对任何其他 GPL 都是如此,但限制什么是通常可用的。 为了避免运行时间成本,你可以在 Foo 中使用 my &private = Bar::get-private; sub public ($input) is export  private ,然后在 Bar 中使用 my sub private  … ; method get-private is implementation-detail  die "NO" unless callframe(1).code.?package.^name eq 'Foo'; &private (未经测试的代码,可能是那里的错字)。这样运行时间成本就会小得多(只需检查一次堆栈)。 “确定的用户可以覆盖&*EXIT 处理程序!”。即使用户可以重载&*EXIT 处理程序,他们也可以复制您的所有模块,进行他们希望进行的任何更改,甚至以完全相同的名称将它们发布到生态系统中。 “作为关键点,而不是丢弃点——人们总是可以剪切/粘贴代码。” @raiph,我不同意这是关键(正如我之前对 jnthn 提到的)。是的,有人可以分叉代码,但是他们有一个不同的程序,可以用它做任何他们想做的事情。 API 隐私是关于不破坏用户代码,复制粘贴我的代码的人永远不会因为我所做的任何更改而破坏代码。而且,当然,警告可以帮助阻止人们依赖实施细节。但是,归根结底,他们仍然会这样做,而且我仍然不想破坏他们的代码。 “我不同意这是关键(正如我之前对 jnthn 提到的)。”这很公平。但我认为你错了,而是同意 jnthn。 “复制粘贴我的代码的人永远不会因为我所做的任何更改而破坏他们的代码。”如果他们剪切/粘贴,然后再次这样做以跟踪您的更改,他们的代码可能会由于您的更改而中断。如果开发人员故意忽略您的公共 API,他们会故意违反合同。你不能阻止他们做他们想做的任何事情。您所能做的就是明确合同的结束。您可以添加律师,也可以添加警察部队,但为什么呢?【参考方案2】:

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

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

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

【讨论】:

> “无论语言提供什么,有足够决心重用内部结构的人都会找到方法(即使是从你的模块中复制粘贴)。”这是一个完全公平的观点。我想我是从Hyrum's law 的角度来看的:如果有人复制粘贴我的代码(我希望他们这样做,或者至少阅读它!),那么他们就有一个 fork,而且我的实现细节的更改不会破坏他们的代码。但是,如果他们忽略了我不使用某些东西的“强烈提示”,那么我可以打破它们——从海伦姆定律的角度来看,这现在是我的 API 的一部分。 我仍然偏爱我的法律免责声明导出标签 ;-)【参考方案3】:

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

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

【讨论】:

使用trust(和Metamodel::Trusting)非常困难,因为必须在编译时完成并且需要某种类型的前向声明(请参阅tio.run/##RY6xCsIwFEX3fMUtZGiXDCIOKQ4pDgqOipsS2icdWluaiJXSf/Fb/…了解它在运行时的样子— 但是,它不起作用,因为 ^add_trustee 在运行时是无操作的) @user0721090601 但我认为 OP 没有明确提及运行时。从理论上讲,既然是在分发级别完成的,那么它可以在编译时完成,对吧? 编译时间很棘手,因为每个模块都需要引用另一个。这不可能完全在编译时完成,因为它会创建循环依赖。也许在 CHECK 阶段,您可以在主(非私有)模块中进行间接引用,但我不确定该引用的效果如何。 (我没有测试过) @user0721090601 你仍然可以在运行时使用 MOP...

Google Maps v3:强制执行最低要求。使用 fitBounds 时的缩放级别

】GoogleMapsv3:强制执行最低要求。使用fitBounds时的缩放级别【英文标题】:GoogleMapsv3:Enforcingmin.zoomlevelwhenusingfitBounds【发布时间】:2011-02-2817:04:44【问题描述】:我正在地图上绘制一系列标记(使用地图API的v3)。在v2中,我有以... 查看详情

.Net 在解决方案级别强制执行依赖块 [关闭]

】.Net在解决方案级别强制执行依赖块[关闭]【英文标题】:.Netenforcingdependencyblocksatsolutionlevel[closed]【发布时间】:2019-06-0714:26:22【问题描述】:解决方案洞察力和问题。我的解决方案中有多个BC,在某些时候将被翻译为微服务。... 查看详情

sequelize + sequelize-hierarchy:如何在每个级别强制执行唯一的组织名称?

】sequelize+sequelize-hierarchy:如何在每个级别强制执行唯一的组织名称?【英文标题】:sequelize+sequelize-hierarchy:Howenforceauniqueorganizationnameperlevel?【发布时间】:2015-09-0418:32:00【问题描述】:我有一个组织树,并希望确保在树的同一... 查看详情

PySpark 分发模块导入

】PySpark分发模块导入【英文标题】:PySparkdistributingmoduleimports【发布时间】:2016-12-1418:36:19【问题描述】:在过去的几天里,我一直在努力了解Spark执行器如何知道如何在导入时使用给定名称的模块。我正在研究AWSEMR。情况:我... 查看详情

有没有办法在 AGM 地图中设置边界和缩放级别?

】有没有办法在AGM地图中设置边界和缩放级别?【英文标题】:IsthereawaytosettheboundsandZoomlevelinAGMMap?【发布时间】:2018-07-2915:42:22【问题描述】:我在我的angular4应用程序中使用AGMmaps,我遇到了一些问题,我将从api获取的多个标... 查看详情

Maven 发布插件:如何在发布时仅部署分发 jar:执行

...【发布时间】:2013-07-1306:30:16【问题描述】:我有一个多模块项目,正在构建的最后一个模块是应用程序的分发zip。core/plugins/plugins/loggerplugins/social...assemble/ 查看详情

在没有数组边界检查、强制转换检查等的情况下运行 Java

】在没有数组边界检查、强制转换检查等的情况下运行Java【英文标题】:RunJavawithoutArrayBoundschecking,Castchecking,etc【发布时间】:2016-05-1312:05:32【问题描述】:我有一个Java程序,它执行许多小数组操作。我已经运行它并验证它不... 查看详情

在 git 中强制执行提交消息格式

...】:如何在Git中强制执行提交消息格式?这可以在存储库级别设置,以便创建分支的每个人都可以执行此操作吗?【问题讨论】:能否请您在上一个问题(***.com/q/37671334/6309)中告诉我们您认为哪个答案更好?(***.com/help/accepted-answ 查看详情

在 Eclipse 中强制执行 JDK8 Doclint

...行新的、更严格的JDK8“doclint”标准,以使Eclipse中的错误级别与javadoc工具相匹配?关于禁用Linter的文章似乎很多,但我想做相反的事情:让它保持打开状态并“修复”我的javadoccme 查看详情

如何在更改其边界的动画期间强制重绘 CALayer

】如何在更改其边界的动画期间强制重绘CALayer【英文标题】:HowtoforceredrawofaCALayerduringananimationthatchangesitsbounds【发布时间】:2012-12-1715:07:43【问题描述】:我有一个托管图层的NSView。其中我有一个包含drawInContext方法的CALayer。nee... 查看详情

Blender Python - 在导入所有类时强制重新加载模块

】BlenderPython-在导入所有类时强制重新加载模块【英文标题】:BlenderPython-Forcereloadofmodulewhileimportingallitsclasses【发布时间】:2021-11-2118:11:55【问题描述】:我正在使用Python/Blender进行开发,这里有两个需求:从我的模块中导入所... 查看详情

SDK 执行任何 API 级别。必须安装组件?

】SDK执行任何API级别。必须安装组件?【英文标题】:SDKdoesanyAPIlevel.componenthavetobeinstalled?【发布时间】:2015-03-1212:27:53【问题描述】:我更换了笔记本电脑,所以我必须再次安装eclipse和androidsdk。我不记得我应该安装每个API级别... 查看详情

跨 C API 边界传递异常

...:如何在边界的一侧捕获异常并在重新跨越CAPI边界并且执行回到C++领域后重 查看详情

在 git 中强制提交消息格式

...】:如何在Git中强制执行提交消息格式?这可以在存储库级别设置,以便创建分支的每个人都可以执行此操作吗?【问题讨论】:能否请您在上一个问题(***.com/q/37671334/6309)中告诉我们您认为哪个答案更好?(***.com/help/accepted-answer 查看详情

强制 Windows 操作系统在执行应用程序之前加载整个应用程序可执行文件 (.exe)

...2-2002:20:11【问题描述】:我有一个想要通过USB闪存驱动器分发的MFC应用程序。应用程序的一项要求是,当用户拔出USB闪存时,MFC应 查看详情

Gradle:分发可执行的混淆 Jar 文件

】Gradle:分发可执行的混淆Jar文件【英文标题】:Gradle:DistributingExecutable,ObfuscatedJarFile【发布时间】:2018-07-1312:59:20【问题描述】:我正在尝试使用带有proguard的gradle来混淆代码,然后生成一个zip文件进行分发。我想使用distributio... 查看详情

如何在 Celery 任务执行期间强制执行记录器格式?

...2019-06-1521:32:26【问题描述】:我有一些使用Python日志记录模块记录调试日志的服务。my_service.py:importlogginglogger=logging.getLogger(__name__)classSomeService 查看详情

Google Maps API v3:未删除标记

...006:03:53【问题描述】:我正在创建一个基于边界框和缩放级别加载和销毁标记的地图。我在正确删除标记时遇到了一个真正的问题,它似乎有时在某些情况下有效。我有一个包含标记信息的对象,其中还包含谷歌地图标记对象。... 查看详情