Consider the following
data Predicate = Pred Name Arity Arguments
type Name = String
type Arity = Int
type Arguments = [Entity]
type Entity = String
This would allow the creation of
Pred "divides" 2 ["1", "2"]
Pred "between" 3 ["2", "1", "3"]
but also the “illegal”
Pred "divides" 2 ["1"]
Pred "between" 3 ["2", "3"]
“Illegal” because the arity does not match the length of the argument list.
Short of using a function like this
makePred :: Name -> Arity -> Arguments -> Maybe Predicate
makePred n a args | a == length args = Just (Pred n a args)
| otherwise = Nothing
and only exporting makePred from the Predicate module, is there a way to
enforce the correctness of the value constructor?
Well, the easy answer is to drop the arity from the smart constructor.
Then if you don’t expose the
Predconstructor from your module and force your clients to go throughmakePred, you know that they will always match, and you don’t need that unsightlyMaybe.There is no direct way to enforce that invariant. That is, you won’t be able to get
makePred 2 ["a","b"]to typecheck butmakePred 2 ["a","b","c"]not to. You need real dependent types for that.There are places in the middle to convince haskell to enforce your invariants using advanced features (
GADTs + phantom types), but after writing out a whole solution I realized that I didn’t really address your question, and that such techniques are not really applicable to this problem in particular. They’re usually more trouble than they are worth in general, too. I’d stick with the smart constructor.I’ve written an in-depth description of the smart constructor idea. It turns out to be a pretty pleasant middle ground between type verification and runtime verification.