Condition Closures

Gavin Wood has a nice article on a coding style to make Solidity contracts easier to understand. The basic idea is to remove all conditional code from function bodies, using modifiers. It's a neat idea, but I realized that once we have a new feature that's coming to Solidity, we might be able to make it even better.

Here's his example, tweaked slightly. It's a token that allows balance transfer and voting, where you need a balance of at least 1000 to vote. He transforms this:

contract Token {   
    mapping (address => uint) balances;
    mapping (address => uint) votes;

    function transfer(uint _amount, address _dest) {
        if (balances[msg.sender] >= _amount) {
            balances[msg.sender] -= _amount;
            balances[_dest] += _amount;
            if (balances[msg.sender] < 1000) {
                votes[msg.sender] = 0;    
            }
        }
    }

    function vote(uint _opinion) {
        if (balances[msg.sender] >= 1000) {
            votes[msg.sender] = _opinion;
        }
    }
}

into this:

contract Token
{
    mapping (address => uint) balances;
    mapping (address => uint) votes;

    modifier hasAtLeast(uint x) {
        if (balances[msg.sender] >= x) _
    }

    modifier hasUnder(uint x) {
        if (balances[msg.sender] < x) _
    }

    modifier voted {
        if (votes[msg.sender] != 0) _
    }

    function clearUndeservedVote()
    hasUnder(1000) voted {
        delete votes[msg.sender];
    }

    function transfer(uint _amount, address _dest)
    hasAtLeast(_amount) {
        balances[msg.sender] -= _amount;
        balances[_dest] += _amount;
        clearUndeservedVote();
    }

    function vote(uint _opinion)
    hasAtLeast(1000) {
        votes[msg.sender] = _opinion;
    }
}

Now the modifier names help document the conditions, and each function just flows linearly from top to bottom. It should make it pretty easy to follow the rules, as long as you follow Gavin's intent and don't put any state updates in the modifier code.

Drawbacks

He admits that it could get verbose though. The problem is that modifiers don't really compose. We have modifiers "hasAtLeast(x)" and "hasUnder(x)". One is just the logical negation of the other, but they both have to be defined separately.

If we change the business rules for his contract slightly, we can run into another problem. Every modifier runs separately, and if you have two modifiers that implement a condition for the contract to run, and attach both modifiers to a function, then both conditions have to be true for the contract to run. What if you want the function to run when either condition is true? You have to make a combined modifier just for that. Let's say that's what we want for clearUndeservedVote() (not that this would be a sensible business rule). It looks like this:

modifier votedOrHasUnder(uint x) {
    if (votes[msg.sender] != 0 || balances[msg.sender] < x) _
}

function clearUndeservedVote(address _who) votedOrHasUnder(1000) {
    delete votes[_who];
}

In a larger contract this could get messy fast.

Back to conditionals

A lot of the same benefit can be obtained with plain ol' function decomposition. Here's a contract written that way, with the "or" rule, and getting rid of the redundant hasAtLeast().

contract Token
{
    mapping (address => uint) balances;
    mapping (address => uint) votes;

    function hasUnder(uint x) returns(bool) {
        return (balances[msg.sender] < x);
    }

    function voted() returns (bool) {
        return (votes[msg.sender] != 0);
    }

    function clearUndeservedVote() {
        if (voted() || hasUnder(1000)) {
            delete votes[msg.sender];
        }
    }

    function transfer(uint _amount, address _dest) {
        if (!hasUnder(_amount)) {
            balances[msg.sender] -= _amount;
            balances[_dest] += _amount;
            clearUndeservedVote();
        }
    }

    function vote(uint _opinion) {
        if (!hasUnder(1000)) {
            votes[msg.sender] = _opinion;
        }
    }
}

Still pretty clear, still easy to audit. But still...not satisfying. Repeating that conditional in every function is annoying. Can't we abstract that out, like we did with modifiers, but still keep the composability of functions? Right now, no. Soon, maybe.

First-class Functions...the Future?

Solidity doesn't have first-class functions right now, but on gitter a certain dev mentioned that they're on the near-term roadmap. I don't know any more and don't even remember the exact quote, but my ignorance won't stop me from speculating wildly about what it might look like, and how we could use it. I'll make up a simple syntax that's in line with what Solidity already does, and assume it all works like first-class functions elsewhere. They're available in languages from Javascript to C# to Haskell.

Suppose we can define a type to be a function signature. For example, we could define a binary arithmetic operation like this:

type BinaryOp = function(int, int) returns (int)

Now a variable can be a BinaryOp. Functions can take BinaryOps as parameters, and return BinaryOps. To make this useful we have to be able to define functions on the fly. For example, we could put a function in a variable like this:

BinaryOp add = function(int x, int y) returns (int) {
    return x + y;
}

What about functions as return values? The trick there is that first-class functions in most languages can "capture" their environment. Here's a classic example:

type UnaryOp = function(int) returns (int)
function addx(uint x) returns (UnaryOp op) {
    op = function(int y) { return x + y; }
}
UnaryOp z = addx(3);

Addx is a function that makes functions. Pass in a number, and it will give you a new function that accepts one parameter and adds your number to it. Since z = addx(3), running z(4) returns 7.

Functions that capture their environment like this are called "closures." (In a true closure, the captured variable is still live in the outer scope, and can be changed. What I'm about to do works just as well if the outer variable is just copied to the inner scope.)

Condition Closures

Back to our example. We need a different type, which we'll call Test. Our functions hasUnder() and voted() are both of type Test:

type Test = function() returns (bool)

And just like that, we can make this magical little contract:

contract Guarded {
    type Test = function() returns (bool)

    modifier guard(Test test) {
        if (test) _
    }

    function either(Test test1, Test test2) returns (Test) {
        return function() returns (Bool) {
            return test1() || test2();
        };
    }

    function both(Test test1, Test test2) returns (Test) {
        return function() returns (Bool) {
            return test1() && test2();
        };
    }

    function not(Test test) returns (Test) {
        return function() returns (Bool) {
            return !test();
        };
    }
}

Guard is a modifier that takes any test as a parameter, and executes the body of the function if the test returns true.

Either() is a function that accepts two tests as inputs, and returns a new test which returns true if either of the input tests returns true. Both() returns a test which returns true when both input tests return true, and Not() returns a test which returns true if the input test returns false.

It's such a generic contract, we can define it once and use it everywhere. Here it is in our token contract:

contract Token is Guarded {
    mapping (address => uint) balances;
    mapping (address => uint) votes;

    function hasUnder(uint x) returns(bool) {
        return balances[msg.sender] < x; 
    }

    function voted() returns (bool) {
        return votes[msg.sender] != 0;
    }

    function clearUndeservedVote() 
    guard(either(hasUnder(1000), voted) (
        delete votes[msg.sender];
    }

    function transfer(uint _amount, address _dest) 
    guard(not(hasUnder(_amount))) {
        balances[msg.sender] -= _amount;
        balances[_dest] += _amount;
        clearUndeservedVote(msg.sender);
    }

    function vote(uint _opinion) 
    guard(not(hasUnder(1000))) {
        votes[msg.sender] = _opinion;
    }
}

No redundancy, no problem mashing tests together in whatever logical combination we need, and no repeated conditionals in the function bodies.

Incidentally, people lately are talking a lot lately about using functional programming in contracts. A big part of functional programming is using first-class functions, just like in the above code.

I hope we'll get to do this soon.