Contracts: Advanced Defensive Programming | Schema Programming Part 26
Types tell you what data is (e.g., "an integer"). Contracts tell you how data should behave (e.g., "an integer greater than zero"). Racket has one of the most advanced contract systems in the world.
Advertisement
Basic Contracts
You use define/contract to attach a contract to a function.
#lang racket
(define/contract (deposit amount)
(-> positive? any) ; Input must be positive, returns anything
(printf "Depositing ~a\n" amount))
(deposit 100) ; OK
; (deposit -50) ; Contract Violation!
Higher-Order Contracts
Contracts can check functions passed as arguments.
(define/contract (apply-twice f x)
(-> (-> number? number?) number? number?)
(f (f x)))
This ensures f is a function that takes a number and returns a number.
Dependent Contracts
What if the output depends on the input?
(define/contract (increment x)
(->i ([arg number?]) [result (arg) (> result arg)])
(+ x 1))
This says: "The result must be greater than the argument".
Blame
The most important feature of contracts is Blame. If a contract is violated, Racket tells you exactly who broke it: the caller (for passing bad input) or the provider (for returning bad output).
Advertisement
Summary
Contracts allow you to enforce "business logic" rules that static types cannot catch. They are helpful for debugging large systems where it's hard to track down which module passed a null or a negative number.
If a function fails to return a value satisfying its output contract, who is blamed?
Md Nasim Sheikh
Software Developer at softexForge