Mark Lynas seems to think we can get the entire world out of poverty without
breaking the planetary boundaries (think carbon, biodiversity, land
use, etc.) by 2050. Cute.
At POPL last week I gave a student lightning talk on
our Virtual Values paper.
The format was four minutes with the slides advancing every 20 seconds. Very
difficult to communicate the important idea in so short a time but it was
really fun and I think turned out pretty well.
Check out the slides if you're interested. I added notes on
each slide that corresponded more or less to what I was saying.
An update to contracts.coffee (0.2.0) is now up on npm. I've fixed up
a few bugs since the intial release and it's now based on the
latest CoffeeScript (1.2.0)
but the big update is a rework of how modules are handled.
Before this update we were handling module blame tracking by forcing the
programmer to call .use() before a contracted value could be used. This
did the work of figuring out who the server and client module.
This was an awkward way to enforce a module pattern since not only do you have to
write more code but it also means you have to know which values in a
module have a contract. This breaks abstraction which is bad.
So with this latest update we're completely dropping .use(). How we keep tack
of the modules without it depends on whether you are using node.js or a browser.
If you are using node.js then the wiring is handled automatically by wrapping
the require function and exports object. If you put a contract on an export
value the correct server and client module names are recorded.
# file Math.coffee
exports.square :: (Num) -> Pos
exports.square = (x) -> x * x
#####
# file Main.coffee
{square} = require './Math'
# vioation blaming Main.coffee
square "a string"
You can also put a contract on a value that isn't on the exports object (for use inside
the module), but the module name might get confusing since it's just the file name with
"(value)" or "(caller)" appened. I will look at adding something like Racket's
nested boundaries
to clean this up at some point.
If you're using the browser, things are a bit more complicated. Since the browser environment
doesn't currently have a standard way to do modules, we're stuck doing the wiring by hand.
Details are in the documentation but the basic idea is that
the library now provides Contracts.exports and Contracts.use which are used to construct
contract aware modules.
Contracts.exports creates and names an exports object:
# Math.coffee
# create and name the exports object
exports = Contracts.exports "Math"
exports.square :: (Num) -> Pos
exports.square = (x) -> x * x
# put the exports object on the global object
# for other modules to see and use
window.Math = exports
And Contracts.use pulls in a module and names the user of the module.
I've been meaning to get around to running some performance benchmarks on contracts.coffee, but I recently learned a valuable lesson: let the internet do your work for you! :-)
I woke up this morning to find @paulmillr had put together a nice performance benchmark of contracts.coffee.
Check it out for all the gory details but suffice it to say contracts makes things...slow. This is about what I'd expected since in addition to running the actual contract check we must wrap the contracted functions/objects in a Proxy which runs handlers for each function call or property set/get. Lots of stuff is in the way of the running code.
So does the slow performance of contracts make them unusable? Not necessarily. First off, the contracts.coffee compiler can emit JavaScript with contracts completely disabled. So, you can have a "slow" testing/development build with contracts enabled to help track down bugs and a production build with contracts disabled and no slowdown.
In addition, most code is IO bound not CPU bound. So if you want contracts enabled everywhere but still care about performance, be smart about where you apply them. Put them on module boundaries, but don't put them on tight loops for example.
In sum, contracts are another tool in the software engineer's toolbox that have advantages (bug squashing) and disadvantages (performance slowdowns when enabled). Use them wisely.
So the response I've been getting for contracts.coffee has been absolutely incredible! There's been tons of positive feedback and some really great excitement about the project.
I've also heard a few worries that contracts are too much like static types and might not be such a good fit for a dynamic language like CoffeeScript or JavaScript. Now, I don't want to get into a huge debate over the merits of static vs. dynamic typing (that way leads to madness and holy wars!) but I would like to clarify a few points and show that contracts actually fit quite nicely into the dynamic ethos of CoffeeScript/JavaScript.
In dynamic languages like CoffeeScript and JavaScript you'll often hear about duck-typing where we don't care about the "type" of an object, just the stuff on the object we actually need to use. So the classic example is a "file-like" object where all we care about are the few properties the code actually uses (like open, read, close, etc.). As long as the properties exist we're good. If a property don't exist and we try to use it, just throw an exception so we can track down the bug.
Contracts allow us to extend this concept quite a lot without sacrificing the awesome flexibility we dynamic language users love about duck-typing. To start out, let's consider the simple case of a missing property. Let's first define a contract for a binary tree:
# A basic binary tree
BinaryTree = ?(Null or {
node: Num
left: Self or Null
right: Self or Null
})
Pretty simple, a binary tree is either null or has a node and two children that are also binary trees. A function can now say it expects a BinaryTree:
f :: (BinaryTree) -> ...
f = (tree) ->
n = tree.node
...
And if we call f with something that isn't quite a binary tree:
Contract violation: expected <{node: Num, left: self or Null,
right: self or Null}>,
actual: "[missing property: node]"
Value guarded in: coffee-script.js:53
-- blame is on: coffee-script.js:56
Parent contracts:
{node: Num, left: self or Null, right: self or Null}
Null or {node: Num, left: self or Null, righ : self or Null}
(Null or {node: Num, left: self or Null, right: self or Null})
-> any
Now we're already winning since the error message is triggered as soon as the function is called with an object that is missing the required properties. If we weren't using contracts, the variable n in the example above would get the value undefined which might not surface as an obvious problem until much later (if ever!).
We're also not losing any of the flexibility of duck-typing
since contracts allow us to specify just the object properties the function needs in order to work properly.
We're just being explicit about how our ducks should quack!
In addition to an early failure, we get lots of information in the error message about what was missing, which contract was violated, and which module was to blame (a powerful notion I explain a little here but which really deserves a future blog post).
But we're just getting warmed up :)
Contracts on objects can also have arbitrary invariants that get checked whenever properties are modified. So we can have a contract for 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)
})
# 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)
})
Note that our contract for a red-black tree is exactly the same as a binary search tree with the addition of the color property and some related invariants. We have a kind of subtyping going on here: a function that expects a binary search tree will also work with a red-black tree but not vica versa.
Now our functions can "duck-type" the invariants on the objects they accept:
takesBST :: (BinarySearchTree) -> Any
takesBST = (bst) -> ...
takesRedBlack :: (RedBlackTree) -> Any
takesRedBlack = (rbTree) -> ...
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!
These functions not only specify that they require certain properties but also that certain invariants must hold. This is really powerful! The function that only requires the behavior of a binary search tree can accept a red-black tree just fine and the function that requires the behavior of a red-black tree but is given just a binary search tree will fail when the invariants are violated (just as we want it to).
So, contracts allow us to be explicit and fail early when things go wrong while still giving us the flexibility of duck-typing. And they work for both "missing-property" kinds of failures along with a more general "failed invariant".
This summer I've been working at Mozilla looking at bringing the
awesomeness that is
contracts to
JavaScript. If you aren't familiar with contracts, think of them as
super powerful asserts.
Why contracts? Because they allow us to build
better software! They allow us to express invariants (things that will
always be true about our code) in JavaScript about JavaScript and
check at runtime that these invariants hold. And when things break
(like they always do) we get a precise error message pin-pointing the
part of our code the broke the contract.
Contracts in programming languages were first popularized in the
language Eiffel. In fact, the people behind Eiffel promoted an entire
design methodology centered around contracts (called, appropriately
enough, Design by Contractâ„¢) that encouraged thinking about invariants and
interfaces between software component boundaries.
So if contracts are so great why don't we see them in JavaScript?
Well, for the most part, the languages that have supported contracts in
the past all share an important characteristic...no lambdas. Or more
specifically in Java and Eiffel functions are not first class (you
can't pass a function in a method call). Traditional contract systems
fall down in a higher order setting (how do you check a function
argument?).
Until, that is, the scheme people (err...I mean
Racket people) figured out
what to do
a few years ago. Now, Racket has a very nice contract system.
But we haven't seen anything comparable to Racket's contracts in
JavaScript so far. So this summer I looked into taking the work that
was done in Racket and trying to fit it into JavaScript. As you
probably know, JavaScript has a bit of scheme in it (just confused by
some C/Java syntax).
The result of this exploration is a JavaScript libarary called
contracts.js and
a fork of the
CoffeeScript compiler
(which translates CoffeeScript directly to JavaScript) called
contracts.coffee.
The JavaScript library gives us the ability to apply contracts to our
code and the CoffeeScript extension gives us some really pretty
syntax.
So what does it all look like?
Here's some CoffeeScript code with contracts:
id :: (Num) -> Num
id = (x) -> x
And the JavaScript translation:
var id =guard(fun(Num, Num),function(x){return x;})
This code takes the identity function and wraps it in a
function contract. Now every time id is called the contract library
first checks that the argument being passed in satisfies the Num
contract (which checks the the value is a number) and that the
result of id also satisfies Num. If any of these checks fail, an
error is thrown. For example:
id("string")
Error: Contract violation: expected <Number>, actual: "string"
Value guarded in: id_module.js:42
-- blame is on: client_code.js:104
Parent contracts: (Number) -> Number
Pretty nifty right?
We can also have 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 various combinations thereof:
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."
You can find documentation, install instructions, and a bunch more
examples for contracts.coffee at its
website. Docs for the
underlying contracts.js library is coming soon but for now just check
out the github page.
This is still very much a work in progress and I would love
feedback. Let me know if you find it useful!