npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

sweet-contracts

v0.1.2

Published

Syntactically Succinct Contracts for JavaScript

Downloads

19

Readme

sweet-contracts

A collection of sweet.js macros that provide contract support for JavaScript! Inspired and motivated by contracts.coffee. Powered by contracts.js. Made possible by sweet.js.

You may notice that the syntax (and documentation...) of sweet-contracts bears a striking resemblance contracts.coffee. This is by design. The idea is to get roughly the same functionality and smooth syntax without transmogrifying existing code into CoffeeScript. Also, we wanted to present something cool you can do with sweet.js which you couldn't otherwise do in pure JS.

###Let's start simple...

Try decorating a regular old JavaScript function with a contract:

fun (Num) -> Num
function dbl(n) {
    return n + n;
}

So what happens if we call dbl incorrectly?

> dbl("qux");
Error: Contract violation: expected <Num>, 
actual: "qux"
Value guarded in: docs.js:3:16 (value) 
-- blame is on: sandbox_exp.js:3:16 (caller)
Parent contracts:
(Num) -> Num 

Awesome! I bet that would have been really hard to debug! How about objects and arrays?

obj { 
      name: Str,
      age:  Num
    }
var person = { name: Alyssa P. Hacker, age: 23 };

arr [Str, Num...]
var scores = ['Bo', 92, 78, 84, 95, 106];

And don't worry, all that whitespace is totally optional. Sweet.js, the macro engine upon which sweet-contracts is based, ignores it. Of course, we can do more complicated things with our contracts:

fun ({name: Str, age: Num}, [Str, Num...]) -> (Num or Bool)
function avg_score(person, scores) {
    var tmp;
    if (person.name !== scores[0]) {
        return false;
    }
    tmp = scores.slice(1).reduce(function(a,b) { 
        return a + b; 
    });
    return (tmp / (scores.slice(1).length));
}

You're not restricted to a set of predefined contract combinators, either. Defining your own is as simple as writing a suitable predicate function.

var Even = check(function(x) { return x % 2 === 0; }, 'Even');

Keep in mind that slapping a contract on your function is not the magic bullet for programming in dynamic languages. The onus is still on you to write semantically robust code, but sweet-contracts aims to lighten the load a bit.

###Usage

NB: I haven't published this as an npm package yet, but you can clone the repository and use the npm infrastructure from inside the project directory.

The best (and easiest) way to use sweet-contracts is to install it via npm:

npm install sweet-contracts

This should take care of any and all dependencies automagically. Now, you can go ahead and create a file test_contracts.js:

var contracts = require("contracts.js");
setupContracts(contracts);

fun (Num) -> Num
function dbl(x) { return x + x; }

console.log(dbl(4));

Go ahead and compile it using sweet-contracts:

$ sweet-contracts -o out.js test_contracts.js
$ node --harmony out.js
8

Notice that you need to require contracts.js and pass it to the setupContracts macro any time you want to compile a file containing sweet-contracts syntax. This makes the module exported by contracts.js available to the sweet-contracts macros. You don't need to worry about this unless you forget those first two lines.

Additionally, contracts.js makes use of some experimental JavaScript features (such as the WeakMap and Proxies). If you want to run the emitted code in node, you will need to pass the --harmony option (the libraries themselves will have been installed by npm). Similarly, if you want to run the code in a browser, you will need to enable experimental JavaScript.

####Testing

If you want to fiddle with this (which we hope you will), just install with the dev dependencies:

npm install --dev

And run the tests:

npm test

###Functions

Basic functions:

fun (Num) -> Num
function f(x) { return x; }

Multiple arguments:

fun (Num, Str, Bool) -> Num
function f(n, s, b) { /*body*/ }

Optional arguments:

fun (Num, Str, Bool?) -> Num
function f(n, s, b) { /*body*/ }

As usual, optional arguments should come at the end of the argument list.

We also have higher order functions:

fun ((Num) -> Bool, Num) -> Bool
function f(g, n) { /*body*/ }

Functions that cannot be used with new:

fun (Num) --> Num
function f(n) { /*body*/ }

var g = f(23);      // oops!
var g = new f(23);  // funky!

And function that must be used with new:

fun (Num) ==> Num
function f(n) { /*body*/ }

var g = f(23);      // oops!
var g = new f(23);  // funky!

We also get dependent functions:

fun (Num) -> !(result, args) -> { return result > args[0]; }
function inc(n) { return n + 1; }

After the function is evaluated, its result and original argument list are passed into the dependent function. If its return value is truthy, then everything is fine. Otherwise, we get a contract violation exception.

The this contract allows us to ensure that, when a function is called, this matches the given contract:

fun (Str) -> Str #{name: Str}
function f(s) { /*body*/ }

var no = { handle: "wotan", f: f};
var ok = { name: "onan", f: f};

no.f(s);            // alright you primitive screwheads...
ok.f(s);            // groovy

###Objects

As you might expect, object properties can be wrapped in various contracts:

obj {
      a: Str,
      b: Num,
      f: (Num) -> Num
    }
var o = {a: "bob", b: 23, f: function (x) -> { return x + 1; };

But take note that object (and array) contracts are not checked until the object it encloses is referenced. That is, you could assign a contract-violating object to o, but you won't get your error until you reference the field which violates the contract.

Nested objects and optional properties:

obj {
      ob: { a: Str },
      a: Num,
      b: ?Str
    } 
var o = { ob: { a: "baz" }, a: 23 }; 

How about a recursive object?

obj {
      a: Num,
      b: Self,
      c: (Num) -> Self,
      inner: { y: Bool, z: Self }
    }
var o = { /*stuff*/ };

The Self contract is built into contracts.js and refers to the closest enclosing object. Remember that the Self contract requires only a reference to a similar object, not a reference to precisely the same object.

Now let's take a look at objects with functions which have pre and post conditions:

obj {
      a: Num,
      f: (Num) -> Num | 
          pre:  !(o) -> { return o.a > 10; },
          post: !(o) -> { return o.a > 20; }
    }
var o = { a: 23, f: function (x) { this.a = this.a + x; } };

The pre and post conditions are parameterized by the enclosing object.

Object invariants currently don't work (on account of a bug in contracts.js where undefined is passed into the invariant predicate), but here's the syntax anyway:

obj {
      a: Num,
      f: (Num) -> Num |
          pre:  !(o) -> { return o.a > 10; },
          post: !(o) -> { return o.a > 20; }
    } | 
     invariant: !(o) -> { 
         return o.a > 0 && o.a < 100; 
     }
var o = { a: 42, f: function (x) { this.a = this.a + x; } };

###Arrays

Basic arrays:

arr [Num, Str, [Bool, Num]]
var a = [1, '2', [true, 23]];

It's worth noting that the outer array contract only covers the first three elements of a, and the inner array contract covers only the first two elements of a[2]. The following array, covered by the same contract, would be just as suitable:

var a = [1, '2', [true, 23, "qux"], "whatever"];

Multiple elements of a single type:

arr [Num...]
var a = [42, 23, 93, 777, 8];

The ... operator ensures that the array will contain only Nums.

Mixed arrays:

arr [Bool, Str, Num...]
var a = [false, "qux", 74, 8, 9]

This requires that the first two elements of the array pass the two single contracts at the beginning. The rest of the elements must be Nums. Note that, like optional function parameters, the contract with the ... operator must be in the last position of the array contract.

###Contract Operators

The or contract:

obj { a: Num or Str }
var o = { a: 8 }

Here o.a must clear either the Num or Str contract. Note that since functions and object/array contracts have deferred checking (i.e. the contract is not checked until the function is called/object field is referenced), only one function/object can be used with or.

The and contract:

obj { a: Num and Even }
var o = { a: 42 }

The a property must pass both contracts. Just as with or, you can't use multiple function/object contracts with the and operator.

It's worth noting that, if you want to use a function contract as an operand, you must enclose it in parentheses. This is a difficulty introduced by the recursive macro definitions used to implement sweet-contracts.

obj { a: Num or ((Str)->Bool) }
var o = { a: function(s) { return s === "quuux"; } };

One final thing is that notions of precedence between and and or, which you might be familiar from Boolean Algebra, are absent from sweet-contracts. In any event, if you want anything more complicated than basic left associativity, you can just use parens.

###Custom Contracts

You can also define your own personal contracts! There are two ways to do this.

Assign a check to a variable:

var Num = check(function(x) { return typeof(x) === 'number'; }, 'Num');

Or let the check macro save you from typing var for the zillionth time:

check(Str, function(x) { return typeof(x) === 'string'; }, 'Str');

The final argument to the check macro, a string, will server to identify the resulting contract in exceptions resulting from its violation.