Manual Modules

Tim Disney edited this page Apr 17, 2012 · 1 revision

Manual Modules

This page deals with wiring modules for correct blame by hand. If you are running contracts.coffee in node.js or the browser with require.js, you'll want to go here.

To wire modules appropriately 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:

# CheckingLibrary.coffee
exports = Contracts.exports "CheckingLibrary"

exports.checkAge :: (Num) -> Bool
exports.checkAge = (age) ->
  # make sure the age makes sense
  age > 0 && age < 150 

window.CheckingLibrary = exports
# Validator.coffee
exports = Contracts.exports "Validator"

exports.validateForm :: ((Str) -> Bool, Str) -> Bool
exports.validateForm = (checker, fieldName) ->
  checker $(fieldName).val()   # failure is here 

window.Validator = exports
# Main.coffee
{checkAge}      = Contracts.use CheckingLibrary, "Main"
{validateForm}  = Contracts.use Validator, "Main"

$("form").submit ->
  # checkAge takes Num but validateForm passes Str!
  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.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.