I've never really used the contract system; however, I figure Robby and
others have probably spent a lot of time making them work, so I should
consider using them. That, and my code might be safer/more
robust/self-documenting/colorfast in hot water.
Although this is just an example, the first function I happened try and
write a contract for was recursive. It wasn't 'fact', but that's OK.
(define/contract fact
(number? . -> . number?)
(lambda (n)
(if (zero? n)
1
(* n (fact (sub1 n))))))
This dies. I skimmed a tech report by Robby and Matthias re: HO
contracts, and see why this is problematic.
This, of course, works:
(module just-the-facts mzscheme
(require (lib "contract.ss"))
(provide/contract (fact (-> number? number?)))
(define (fact n)
(if (zero? n)
1
(* n (fact (sub1 n))))))
Welcome to DrScheme, version 350.
Language: Textual (MzScheme, includes R5RS).
> (require just-the-facts)
> (fact 5)
120
> (fact 'a)
6:3: top-level broke the contract (-> number? number?) on fact; expected
<number?>, given: a
Now, a question about the design of modules with contracts. I don't know
what kind of overhead contracts place on code: I assume it is either
minimal, or noteworthy (meaning "enough that it might matter").
Therefore, it is common or bad practice to write code like:
(module just-fact mzscheme
(provide fact)
(define (fact n)
(if (zero? n)
1
(* n (fact (sub1 n))))))
(module fact-with-contract mzscheme
(require (lib "contract.ss"))
(require just-fact)
(provide/contract (fact (-> number? number?))))
(module fact-without-contract mzscheme
(require just-fact)
(provide fact))
Assuming that this code gets used like:
Welcome to DrScheme, version 350.
Language: Textual (MzScheme, includes R5RS).
> (require (prefix fwc: fact-with-contract))
> (fwc:fact 5)
120
> (fwc:fact 'a)
6:3: top-level broke the contract (-> number? number?) on fwc:fact;
expected <number?>, given: a
> (require (prefix fw/oc: fact-without-contract))
> (fw/oc:fact 5)
120
> (fw/oc:fact 'a)
. zero?: expects argument of type <number>; given a
That is, the module with contracts and the module without contracts
provide identical exports, but one attaches contracts to the exports,
while the other does not.
I can think of plenty of reasons why this is not a good idea; are there
any reasons it is a good idea, and does anyone use the module/contract
system this way in practice?
Cheers,
M