我在hackage上看过几个包含模块名称的包,.Internal作为它们的姓氏组件(例如Data.ByteString.Internal)
这些模块在Haddock中通常不能正常浏览(但它们可能会显示),不应该被客户端代码使用,而是包含从暴露的模块重新导出或仅在内部使用的定义.
现在我对这个图书馆组织模式的问题是:
.Internal模块解决了哪些问题?.Internal模块中?.Internal模块的帮助下组织图书馆的当前推荐做法是什么?dfl*_*str 25
Internal模块通常是暴露封装内部的模块,它打破封装封装.
举ByteString个例子:当你通常使用ByteStrings时,它们被用作不透明的数据类型; 一个ByteString值是原子的,它的表示是无趣的.所有的函数都Data.ByteString取值ByteString,而不是原始Ptr CChar的东西.
这是件好事 ; 这意味着ByteString作者设法使表示足够抽象,以便ByteString可以完全隐藏用户的所有细节.这种设计导致功能封装.
这些Internal模块适用于希望使用封装概念内部的人员,以扩大封装范围.
例如,您可能希望创建一种新的BitString数据类型,并希望用户能够将a转换ByteString为a BitString而不复制任何内存.为了做到这一点,你不能使用opaque ByteStrings,因为这不会让你访问代表它的内存ByteString.您需要访问指向字节数据的原始内存指针.这是s 的Internal模块ByteString提供的.
然后,您应该将BitString数据类型封装起来,从而扩大封装而不会破坏它.然后,您可以自由地提供自己的BitString.Internal模块,为可能希望依次检查其表示的用户公开数据类型的内部.
如果某人没有提供Internal模块(或类似的),则无法访问模块的内部表示,并且用户编写例如BitString强制(ab)使用诸如unsafeCoerce内存指针之类的东西,并且事情变得丑陋.
应放在Internal模块中的定义是数据类型的实际数据声明:
module Bla.Internal where
data Bla = Blu Int | Bli String
-- ...
module Bla (Bla, makeBla) where -- ONLY export the Bla type, not the constructors
import Bla.Internal
makeBla :: String -> Bla -- Some function only dealing with the opaque type
makeBla = undefined
Run Code Online (Sandbox Code Playgroud)
luq*_*qui 19
@dflemstr是对的,但不明确以下几点.一些作者将一个包的内部放在一个.Internal模块中,然后不通过cabal暴露该模块,从而使客户端代码无法访问它. 这是一件坏事1.
暴露的 .Internal模块有助于传达模块实现的不同抽象级别.替代方案是:
(1)使文档混乱,并使用户很难告诉他的代码之间的转换,尊重模块的抽象和破坏它.这种转变很重要:它类似于将参数移除到函数并用常量替换它的出现,失去一般性.
(2)使上述转换不可能并阻碍代码的重用.我们希望尽可能使代码尽可能抽象,但(参见爱因斯坦)不再如此,模块作者没有模块用户那么多的信息,因此无法决定哪些代码应该是不可访问的.有关此论点的更多信息,请参阅链接,因为它有点特殊和有争议.
公开.Internal模块提供了一种快乐的媒介,它可以在不强制执行的情况下传达抽象障碍,允许用户轻松地将自己限制为抽象代码,但允许他们在抽象中断或不完整时"扩展"模块的使用.
1当然,这种纯粹的判断有并发症.内部更改现在可以破坏客户端代码,现在作者有更大的义务来稳定他们的实现以及他们的接口.即使它被正确放弃,用户也是用户并得到支持,因此隐藏内部结构有一些吸引力.它需要一个区分.Internal和接口更改的自定义版本策略,但幸运的是,这与版本控制策略一致(但不明确)."真正的代码"也是出了名的懒惰,所以.Internal当有一种抽象的方法来定义只是"更难"的代码(但最终支持社区的重用)时,暴露一个模块可以提供一个简单的方法.它还可以阻止报告抽象界面中的遗漏,而这些遗漏实际上应该推送给作者修复.
我们的想法是,您可以使用从中导出的"正确",稳定的API,MyModule这是使用该库的首选和记录方式.
除了公共API之外,您的模块可能还有私有数据构造函数和内部帮助函数等.MyModule.Internal子模块可用于导出这些内部函数,而不是将它们完全锁定在模块中.