Design by Contract is a technique for designing interfaces that specify pre and post conditions on methods and invariants on objects. Playing around with some of Scala’s features that support extensibility, I implemented pre and post conditions (require and ensure) as follows:

trait Contracted
{
	class AssertionFailed extends Error
 
	type Conds=List[()=>boolean]
	
	protected case class Contract(reqs:Conds,enss:Conds) {
	
		def require(test: =>boolean)=Contract((()=>test)::reqs,enss)
		
		def ensure(test: =>boolean)=Contract(reqs,(()=>test)::enss)
		
		def in[T](body: =>T):T={
			for(r<-reqs.reverse if(!r()))throw new AssertionFailed()
			val ret=body
			for(e<-enss.reverse if(!e()))throw new AssertionFailed()
			ret
		}			
	}
 
	def require(test: =>boolean)=Contract((()=>test)::Nil,Nil)
 
	def ensure(test: =>boolean)=Contract(Nil,(()=>test)::Nil)
}

These need calling in a slightly convoluted way due to how Scala is parsed:

class Account(b: Int) extends Contracted
{
	var balance = b
 
	def withdraw(amount: Int)
	{
		val old_balance=balance
		(
			require(amount>0)
			require(balance-amount>=0)
			ensure(old_balance-amount==balance)
		) in {
			balance-=amount
		}
	}
	
	def credit(amount: Int)
	{
		val old_balance=balance
		(//Note these can come in any order, although all ensures then all requires in more logical
			ensure(balance<=1000) //Fairly arbitrary restriction as an example
			ensure(balance>=0)
			require(amount>0)
			ensure(old_balance+amount==balance)
		) in {
			balance+=amount
		}
	}
	
	def print()
	{
		println("Balance:"+balance)
	}
}

By putting all of the require/ensure in brackets, the first one calls the corresponding function and returns a Contract object, and then the subsequent ones call methods in the contract object. If you put a . in front of each method, you no longer need the brackets. “in” must be on the same line as the close bracket or have a . in front of it, otherwise the compiler doesn’t realise “in” applies to the previous object. Also, construction of old values is a little manual but not too kludgey.

To run the example, here is a little program that fails near the end:

object DBCTest extends Application
{
	override def main(args: Array[String])
	{
		val a=new Account(100)
		a.print()
		a.withdraw(50)
		a.print()
		a.withdraw(50)
		a.print()
		a.credit(100)
		a.print()
		a.credit(1000)
		a.print()
	}
}
 
DBCTest.main(args)
 
code/design-by-contract.txt · Last modified: 2007/10/31 02:42 by 131.111.236.124
 
Recent changes RSS feed Valid XHTML 1.0 Driven by DokuWiki