contracts.coffee
Contracts.coffee is a dialect of CoffeeScript with built-in support for contracts. It is inspired by the contract system found in Racket.
Contracts let you clearly—even beautifully—express how your code behaves, and free you from writing tons of boilerplate, defensive code.
You can think of contracts as assert on steroids.
Basics
Here’s a simple example of a contract on a function:
id :: (Num) -> Num
id = (x) -> x
This says that the id function should always be called with a number and will always return a number. It looks a lot like types (in fact the syntax looks a lot like Haskell) but unlike types, contracts are enforced at runtime in pure JavaScript.
If we try to use id incorrectly:
id "foo"
The program throws an error, which displays lots of nice information telling us what we did wrong:
Error: Contract violation: expected <Num>, actual: "foo" Value guarded in: id_module:42 -- blame is on: client_code:104 Parent contracts: (Num) -> Num
You can also put contracts on objects.
person ::
name: Str
age: Num
person =
name: "Bertrand Meyer"
age: 42
And arrays.
loc :: [...Num]
loc = [99332, 23452, 123, 2, 5000]
And in various combinations.
average :: ({name: Str, age: Num}, [...Num]) -> Str
average = (person, loc) ->
sum = loc.reduce (s1, s2) -> s1 + s2
"#{person.name} wrote on average
#{sum / loc.length} lines of code."
Under the covers, contracts are really just normal functions that return true or false, so it’s really easy to roll your own by using the ! operator to define new contracts.
Even = (x) -> x % 2 is 0
Odd = (x) -> x % 2 isnt 0
addEvens :: (!Even) -> !Odd
addEvens = (x) -> x + 1
In fact, since contracts are checked at runtime, they can enforce properties that static type systems can only dream of.
Prime = (x) -> # ...
f :: (!Prime) -> !Prime
f = (x) -> x
Eat your heart out, Haskell :)
Quick Start
Here’s what you need to actually start working with contracts.coffee.
First, if you don’t already have them, install Node.js and npm. Then install contracts.coffee using npm:
npm install -g contracts.coffee
Now, compile some coffee with contracts!
coffee -c --contracts MyContractedScript.coffee
Note the -c and --contracts flags. The -c flag says to compile to JavaScript and --contracts enables contracts in the compiled JavaScript. If you don’t want contracts enabled (say in production) simply don’t include the --contracts flag.
If you are planning to run your code in the browser you will need to include the contracts.js library (which can be found here). So the header of your HTML file will look something like:
...
<script src="lib/contracts.js"
type="application/javascript"></script>
<script src="MyContractedScript.js"
type="application/javascript"></script>
...
If you are planning to run your code on node.js then you simply need to install contracts.js via npm:
npm install contracts.js
Note that if you have an existing install of CoffeeScript, installing contracts.coffee will replace it. If you don’t want to give up the old CoffeeScript compiler you can grab the source from github and just run contracts.coffee from its own directory:
bin/coffee -c --contracts MyContractedScript.coffee
And finally, note that contracts.coffee requires some pretty new features of JavaScript to get its job done (in particular Proxies) so it currently only works on Firefox 4+ and Node.js 0.5.10 (but not 0.6.x yet). Proxy support is in V8 but it hasn’t worked it way into Chrome just yet.
When using node you will need to supply two command line flags to enable Proxies (--harmony_proxies) and WeakMaps (--harmony-weakmaps). If you use the coffee or cake scripts these flags will be enabled automatically for you, otherwise the full process looks like:
coffee -c --contracts script.coffee
node --harmony_proxies --harmony-weakmaps script.js
Note that since leaving off the --contracts flag will generate JavaScript code with absolutely no trace of contracts (the code is exactly what vanilla CoffeeScript would generate), you can easily set up a production build with contracts disabled that can run in any browser or JavaScript environment and a development/testing build with contracts enabled that you run in Firefox to help track down bugs.
Resources
- Issue Tracker
For filing bugs, requesting features and changes. - Google Group
For general discussion about contracts.coffee. - disnet’s blog
Will sometimes post about contracts.coffee.
How to Use
In order to provide good error messages when things go wrong, contracts.coffee needs to know where contracted values are created and used in your code. It does this by enforcing a kind of module discipline and keeping track of which module a value was in when it was first wrapped up in a contract and which module uses the contracted value.
If you are running in node.js the appropriate module wiring is done automatically. You just need to use require and the exports object like normal.
If you are in the browser, some hand wiring is needed (future versions of contracts.coffee will automate this better). To do this the library provides two utility functions: Contracts.exports and Contracts.use.
The Contracts.exports(moduleName) function creates an empty object for you to use much like the node.js exports object. Any contracted values you add to it will reference moduleName in any contract violation messages.
# Library.coffee
# create and name the exports object
exports = Contracts.exports "Library"
exports.id :: (Num) -> Num
exports.id = (x) -> x
# put the exports object on the global object
# for other modules to see and use
window.MyLib = exports
The Contracts.use(exportObject, moduleName) function takes an object created by Contracts.exports (or a normal object) and assigns the correct user module name for use in later error messages.
# Main.coffee
{id} = Contracts.use window.MyLib, "Main"
id 4 # ok
id "foo" # Contract Violation...
Contract violation: expected <Num>, actual: "foo" Value guarded in: Library -- blame is on: Main Parent contracts: (Num) -> Num
Admittedly, in the example above finding the right module name is pretty trivial (we could have just inspected the stacktrace). To see the real power of recording the right names with exports/use consider the following example:
1 # CheckingLibrary.coffee
2 exports = Contracts.exports "CheckingLibrary"
3
4 exports.checkAge :: (Num) -> Bool
5 exports.checkAge = (age) ->
6 # make sure the age makes sense
7 age > 0 && age < 150
8
9 window.CheckingLibrary = exports
1 # Validator.coffee
2 exports = Contracts.exports "Validator"
3
4 exports.validateForm :: ((Str) -> Bool, Str) -> Bool
5 exports.validateForm = (checker, fieldName) ->
6 checker $(fieldName).val() # failure is here
7
8 window.Validator = exports
1 # Main.coffee
2 {checkAge} = Contracts.use CheckingLibrary, "Main"
3 {validateForm} = Contracts.use Validator, "Main"
4
5 $("form").submit ->
6 # checkAge takes Num but validateForm passes Str!
7 validateForm checkAge, "#age"
We get this contract violation:
Contract violation: expected <Num>, actual: "42" Value guarded in: CheckingLibrary -- blame is on: Main Parent contracts: (Num) -> Bool
Here we have a library that does some simple form validation. The checkAge library function checks ages to be within a reasonable range for humans and the validateForm function takes a field name and a checker function and checks the field’s value with the supplied checker.
The problem happens when Main.coffee sets up the form submit handler to call validateForm (which expects a checker that takes strings) with checkAge (which takes numbers).
Notice that the violation happens at Validator.coffee:4 when the checker is called with a string but the module at fault is actually Main.coffee (since it was responsible for providing Validator.coffee with the right checker). In this case it is pretty much impossible to correctly assign blame by just inspecting the stacktrace since the the point of failure is in a different location than the file actually at fault.
But the error message gets it right!
It gets it right because we setup the module names with exports/use which allows the system to blame the offending module.
Simple Contracts
In addition to the Num contract that checks for numbers, we also have Str, Bool, Null, Undefined, Nat, Pos, Neg, Any (everything is ok), and None (nothing is ok).
Functions
Basic functions:
f :: (Num) -> Num
f = (x) -> x
Multiple arguments:
f :: (Num, Str, Bool) -> Num
f = (n, s, b) -> # ...
Optional arguments:
f :: (Num, Str, Bool?) -> Num
f = (n, s, b) -> # ...
All optional arguments must come at the end of the arguments list.
Higher order functions:
f :: ((Num) -> Bool, Num) -> Bool
f = (g, n) -> # ...
Functions that cannot be called with the new keyword:
f :: (Num) --> Num
f = (n) -> # ...
#...
g = f 42 # ok
g = new f 42 # error!
Functions that can only be called with the new keyword:
f :: (Num) ==> Num
f = (n) -> # ...
#...
g = f 42 # error!
g = new f 42 # ok
Dependent functions:
inc :: (Num) -> !(result) -> result > $1
inc = (n) -> n + 1
The variable $1 is the first argument passed to the function ($2 would be the second argument, $3 the third, and so on). This allows us to compare the result of the function to its arguments. Note that the test is run after the function has completed so if any of the arguments were mutated during the function’s execution the test could give spurious results.
The this contract:
f :: (Str, @{name: Str}) -> Str
f = (s) -> #...
o = { name: "foo", f: f}
o.f()
Checks that this matches the given object contract.
Objects
Simple properties:
o ::
a: Str
b: Num
f: (Num) -> Num
o =
a: "foo"
b: 42
f: (x) -> x
Optional properties:
o ::
a: Str
b: Num?
f: (Num) -> Num
o =
a: "foo"
f: (x) -> x
Nested objects:
o ::
oo: { a: Str }
b: Num
o =
oo: { a: "foo" }
b: 42
Recursive objects:
o ::
a: Num
b: Self
c: (Num) -> Self
inner: { y: Bool, z: Self }
o = #...
Self binds to the closest object contract. So in this example, Self in b and c points to o and Self in inner.z points to inner.
Objects with functions that have pre and post conditions:
o ::
a: Num
f: (Num) -> Num -|
pre: (o) -> o.a > 10
post: (o) -> o.a > 20
o =
a: 12
f: (x) -> @.a = @.a + x
The pre and post condition functions are called with the object that f is a member of. As their names imply, pre is called before the function f is invoked and post is called after.
Object invariants:
o ::
a: Num
f: (Num) -> Num -|
pre: (o) -> o.a > 10
post: (o) -> o.a > 20
-| invariant: ->
@.a > 0 and @.a < 100
o =
a: 12
f: (x) -> @.a = @.a + x
The invariant is checked at contract application and whenever there is a possibility of o mutating (on property sets and delete).
Arrays
Basic arrays:
a :: [Num, Str, [Bool, Num]]
a = [42, "foo", [true, 24]
This says the array must have three elements, the first being a Num, the second being a Str, and the third being another array.
Multiple elements:
a :: [...Num]
a = [42, 24, 432, 854, 21]
The ... operator says that the array will only contain Nums.
Mixing arrays
a :: [Bool, Str, ...Num]
a = [false, "foo", 432, 854, 21]
The ... operator can be mixed with single contracts. This says that the array’s first and second positions must pass the first two contracts and the remaining array positions must pass Num. The ... operator must be in the last position of the array contract.
Contract Operators
The or contract:
o :: { a: Num or Str }
o = { a: 42 }
Here, the a property must pass either the Num or Str contract. Note that since contracts like function and object have deferred checking, they cannot be used with the or contract. Or to be more precise only one function/object contract can be used with or. So you could have Num or (Num) -> Num but not ((Num) -> Num) or ((Str) -> Str). If you combine normal contracts and function/object contracts with or all the normal contracts will be checked first and then the function/object contract will be applied.
The and contract:
o :: { a: Num and Even }
o = { a: 42 }
The a property must pass both the Num and Even contracts. Just like or you cannot use multiple function/object contracts with and.
Naming your own contracts
You can bind a contract to a variable just like normal expressions:
NumId = ?(Num) -> Num
f :: NumId
f = (x) -> x
The ? operator allows you to escape out of the normal expression language and into the contract language.
You can also escape from the contract language to the normal expression language:
takesEvens :: (!(x) -> x % 2 is 0) -> Num
takesEvens = (x) -> x
The result of the expression in the ! escape must be a function that returns a boolean. It is converted to a contract that checks its value against the function.
The standard Num and Str contracts you have seen are implemented as:
Num = ?!(x) -> typeof x is 'number'
Str = ?!(x) -> typeof x is 'string'
The syntax may look a little strange at first, but it can be read as “assign to Num the contract generated from the function (x) ->
...
It could also be written:
Num = (x) -> typeof x is 'number'
Str = (x) -> typeof x is 'string'
f = (!Num) -> !Str
Duck-Typing Invariants
A full write-up on this topic is covered here but to whet your appetite: you can “duck-type” object invariants. Code can now say, “give me whatever object you want so long as it has these properties and satisfies these invariants”.
Consider a binary search tree:
# A binary search tree is a binary tree
# where each node is greater than the
# left child but less than the right child
BinarySearchTree = ?(Null or {
node: Num
left: Self or Null
right: Self or Null
-| invariant: ->
(@.node > @.left.node) and (@.node < @.right.node)
})
And a red-black tree:
# A red-black tree is a binary search tree
# that keeps its balance
RedBlackTree = ?(Null or {
node: Num
color: Str
left: Self or Null
right: Self or Null
-| invariant: ->
(@.color is "red" or @.color is "black") and
(if @.color is "red"
(@.left.color is "black" and
@.right.color is "black")
else
true
) and
(@.node >= @.left.node) and
(@.node >= @.right.node) and
})
The red-black tree is exactly the same as a binary search tree with some additional invariants. This means we have a kind of subtyping going on here: code that expects a binary search tree will also work with a red-black tree but not vica versa.
takesBST :: (BinarySearchTree) -> Any
takesBST = (bst) -> ...
takesRedBlack :: (RedBlackTree) -> Any
takesRedBlack = (rbTree) -> ...
bst = makeBinarySearchTree()
rb = makeRedBlackTree()
takesBST bst # works fine
takesBST rb # works fine
takesRedBlack rb # works fine
takesRedBlack bst # might fail if the full
# red-black invariants don't hold!
In duck-typing, functions work when given any object that has the properties the function needs (though the object might have other properties too). Contracts allow us to extend that to object invariants: functions work when given any object that has the required properties and satisfies the required invariants (though the object might satisfy other invariants too).