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.
makePred :: Name -> Arguments -> Predicate
makePred name args = Pred name (length args) args
Then if you don't expose the Pred
constructor from your module and force your clients to go through makePred
, you know that they will always match, and you don't need that unsightly Maybe
.
There is no direct way to enforce that invariant. That is, you won't be able to get makePred 2 ["a","b"]
to typecheck but makePred 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 (GADT
s + 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With