我会提出一个不同的、明显比大多数人更严格的规则。中心标准是:
你保证这种类型永远不会改变吗?如果是这样,暴露构造函数可能是一个好主意。不过,祝你好运!
但是您可以保证的类型往往是非常简单的通用“基础”类型,例如Maybe
, Either
or []
,可以说可以编写一次,然后再也不会重新访问。
尽管即使是那些也可能会受到质疑,因为它们确实会不时地被重新审视;出于性能原因,有些人在各种情况下都使用了 Church 编码的版本Maybe
,例如:List
{-# LANGUAGE RankNTypes #-}
newtype Maybe' a = Maybe' { elimMaybe' :: forall r. r -> (a -> r) -> r }
nothing = Maybe' $ \z k -> z
just x = Maybe' $ \z k -> k x
newtype List' a = List' { elimList' :: forall r. (a -> r -> r) -> r -> r }
nil = List' $ \k z -> z
cons x xs = List' $ \k z -> k x (elimList' k z xs)
这两个示例强调了一些重要的事情:您可以将Maybe'
上面显示的类型的实现替换为任何其他实现,只要它支持以下三个功能:
nothing :: Maybe' a
just :: a -> Maybe' a
elimMaybe' :: Maybe' a -> r -> (a -> r) -> r
...以及以下法律:
elimMaybe' nothing z x == z
elimMaybe' (just x) z f == f x
这种技术可以应用于任何代数数据类型。对我来说,与具体构造函数的模式匹配不够抽象;它并没有真正为您带来任何您无法摆脱抽象构造函数 + 析构函数模式的东西,并且它失去了实现的灵活性。