在值构造函数上指定不变量

时间:2011-01-26 22:00:59

标签: haskell constructor invariants

考虑以下

data Predicate = Pred Name Arity Arguments

type Name      = String
type Arity     = Int
type Arguments = [Entity]
type Entity    = String

这将允许创建

Pred "divides" 2 ["1", "2"]
Pred "between" 3 ["2", "1", "3"]

但也是“非法”

Pred "divides" 2 ["1"]
Pred "between" 3 ["2", "3"]

“非法”因为arity与参数列表的长度不匹配。

没有使用像这样的功能

makePred :: Name -> Arity -> Arguments -> Maybe Predicate
makePred n a args | a == length args = Just (Pred n a args)
                  | otherwise = Nothing

并且只从Predicate模块导出makePred,有没有办法 强制执行值构造函数的正确性?

2 个答案:

答案 0 :(得分:6)

嗯,简单的答案就是从聪明的构造函数中删除arity。

makePred :: Name -> Arguments -> Predicate
makePred name args = Pred name (length args) args

然后,如果您没有公开模块中的Pred构造函数并强制您的客户通过makePred,那么您知道它们将始终匹配,并且您不需要那么不雅观{ {1}}。

没有直接方式来强制执行该不变量。也就是说,您将无法获得Maybe来进行类型检查,但makePred 2 ["a","b"]无法进行类型检查。你需要真正的依赖类型。

中间有一些地方可以说服haskell使用高级功能(makePred 2 ["a","b","c"] s +幻像类型)来强制执行你的不变量,但在写完一个完整的解决方案后,我意识到我并没有真正解决你的问题,并且这些技术特别不适用于这个问题。他们通常也比一般的价值更麻烦。我坚持使用智能构造函数。

我写过an in-depth description of the smart constructor idea。事实证明,在类型验证和运行时验证之间,这是一个非常令人愉快的中间地带。

答案 1 :(得分:0)

在我看来,如果您希望所述限制可以强制执行,那么您应该使Predicate成为一个类,并且每种谓词都有自己的数据类型,即Predicate的实例。

这样你就可以在谓词中使用除String类型之外的参数。

像这样(UNTESTED)

data Entity = Str String | Numeric Int

class Predicate a where
    name :: a -> String
    arity :: a -> Int
    args :: a -> [Entity]
    satisfied :: a -> Bool

data Divides = Divides Int Int
instance Predicate Divides where
    name p = "divides"
    arity p = 2
    args (Divides n x) = [(Numeric n), (Numeric x)]
    satisfied (Divides n x) = x `mod` n == 0

这可能会或可能不会解决您的问题,但它肯定是一个很好的选择。