我最近发现了Haskell的称为“模块签名”的功能。如我所知,它们被放入.hsig
文件中,并以signature
关键字而不是module
开头。
这样的文件的示例语法可能看起来像
signature Str where
data Str
empty :: Str
append :: Str -> Str -> Str
但是,我无法想象人们将如何以及为什么使用它们。您能解释一下它们解决了哪些问题以及如何正确利用它们吗?
他们强烈提醒我可以在OCaml(link)中看到的模块系统,该系统也具有模块签名和单独的实现,但是我无法确定这两个概念有多接近。有什么关系吗?
答案 0 :(得分:4)
它们与OCaml模块系统有关,但有一些重要区别:
签名是在语言中定义的(在 .hsig 文件中),但与OCaml不同,它们不是在语言中实例化的。相反,包管理器控制实例化(当前,仅Cabal提供实例化)。模块从不知道它们是要导入抽象签名还是实际模块。
实现模块对签名一无所知,并且不直接引用它们。如果定义恰好兼容,则任何现有模块都可以实现签名。
实例化是由某个编译单元(可执行文件,库,测试套件...)中的依赖项中模块名称和签名名称的重合触发的。当名称重合时,将发生一个称为“签名匹配”的过程验证类型和定义是否兼容。
“幸福的道路”是,在您的程序中,您依赖于具有签名“漏洞”的某个库,并且还依赖于另一个提供具有相同名称的实现模块的库。然后签名匹配自动发生。如果名称不匹配,或者我们需要使用签名的库的多个实例,则必须在Cabal文件的mixins部分中重命名签名和/或模块。
关于为什么模块签名可能有用的原因,请考虑bytestring,这是迄今为止在Haskell中处理二进制数据最流行的库。但是还有其他类型,例如stdio类型为Bytes
。
假设您正在编写自己的使用二进制数据的库,并且不想强迫用户进入stdio或bytestring。您有什么选择?
Bytelike
的类,并使用它对所有函数进行参数化。您还需要向每个包含字节的每个数据类型添加一个类型参数。 从用户的角度来看,typeclass解决方案并不令人满意。用户知道他想使用ByteString
或Bytes
之一。该决定将不依赖于某些运行时标志,并且将在 his 程序范围内保持不变。然而,他必须处理更复杂的API,这使他不时回想起已经确定的问题。
最好是先做出决定,然后将其写入.cabal文件,然后再处理一个更简单的API。
答案 1 :(得分:1)
如here所述,它们与OCaml模块签名密切相关。它们使您可以创建缺少某些模块的软件包,并说包含这些类型和值的模块应由软件包的用户交付。我还没有亲自测试过,但我想像这样的软件包非常类似于OCaml函子。