考虑以下
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,有没有办法 强制执行值构造函数的正确性?
答案 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
这可能会或可能不会解决您的问题,但它肯定是一个很好的选择。