我看到的大多数Haskell代码都使用了直接结构,例如列表和树。例如,Haskeller通常会写:
fillRect :: Color → Bounds → Image → Image
该模式存在问题:如果稍后程序员决定修改“Image”的定义,或者使用其他数据结构,那么他将不得不使用它重构每一段代码。在OCaml中,您可以简单地使用一个模块来指定Image的接口,然后再决定具体的实现。
什么是OCaskl模块的Haskell替代方案?
答案 0 :(得分:12)
有几种选择;没有完全匹配ML模块,但每个方面都有。
参数多态性。您可以在fillRect
上参数化您的模块和Image
函数,而不是在那些参数上对抽象Image
类型 -constructor 进行参数化。指定特定的"种类"图像所以这将是一个像
fillRect_ppm :: ImageImplemetation i => Colour -> Bounds -> Image i -> Image i
其中ImageImplemetation
是指定转换和/或后端函数之类的类型类
使用这样的解决方案,您无法完全替换 Image
类型,但您可以使该类型本身具有任意灵活性。可能,您Image
所使用的所有具体类型实际上都会共享某些字段,因此最好不要使其比必要更灵活:ImageImplemetation
的不同实例只需要实现他们之间的不同。如果实现非常相似,您可能希望仅对颜色空间等一些细节进行参数化。
当然,您需要使用此解决方案提前计划,但仅适用于通用接口。如果您定义了必要的实例,您仍然可以稍后将任何类型作为参数。
同样(也可以混合使用),你可以为类型提供一个可以保存图像数据的类型。
fillRect_tcl :: Image img => Colour -> Bounds -> img -> img
Image
类将直接定义一些原语,如如何绘制线条,fillRect_tcl
将在其实现中使用这些原语。很好,因为fillRect_tcl
适用于任何此类类型。有点不好的是,Image
类需要相当笨拙(它的首选使用类型类用于"数学"有简单明确的法律的东西。)
也许该类甚至可以将矩形作为一种方法:
class Image img where
...
fillRect_tcm :: Colour -> Bounds -> img -> img
...
不太好,您需要为任何img
实例完全重新定义该方法。
Haskell的模块系统不能做很多事情,但它仍然足以让你免于重构一切。例如,如果Image
来自模块Data.Image
,那么您可以完成
import qualified Data.Image as IM
fillRect_qfi :: Colour -> Bounds -> IM.Image -> IM.Image
并使用该模块中定义的所有函数,也可以使用限定符。如果在某些时候您决定切换实施,您只需更改导入即可 当然,对于类型之间的切换定期来说,这并不是很好,但对于一次性更改则非常好。
非常适合您的示例:diagrams同时使用前两个点;例如rect使用TrailLike
类来实现其原语,此类包含as" final"图表类型QDiagram
,在您用于渲染的后端进行参数化。