Tuesday, September 15, 2009

SICP Constraint Network Impl code in Scala

I wrote the code for digital circuit simulator from SICP to contrast my scala coding style with that of Martin Odersky(he wrote same in his book "Programming in Scala"). These are the two(part-I, part-II) posts about that.

To enhance my learnings and to write some more code, Now I implemented the constraint propagation network from SICP section-3.3.5 too.

Here is the code...
//all the Constraint implementations
//extend the Constraint class
abstract class Constraint {
def informAboutValue()
def informAboutNoValue()
}

//Connector implementation
class Connector {
private var myName = "Unknown"
private var myVal:Int = _
private var informant:AnyRef = _
private var constraints:List[Constraint] = List()

def this(newName:String) {
this()
myName = newName
}

def name = myName

def hasValue() = informant != null

def value = {
if(!hasValue())
throw new RuntimeException("""|This connector
| does not have a
| value.""".stripMargin)
myVal
}

def setValue(newVal:Int, informer:AnyRef) {
Tuple(hasValue(),newVal == myVal) match {
case Tuple2(false, _) => {
myVal = newVal
informant = informer
constraints.foreach(
(c:Constraint) =>
{ if(c ne informer) c.informAboutValue()})
}
case Tuple2(true,true) => ; //ignore
case Tuple2(true,false) =>
throw new RuntimeException("Contradiction " +
newVal + " " + myVal)
}
}

def forgetValue(retractor:AnyRef) {
if(retractor eq informant) {
informant = null
constraints.foreach(
(c:Constraint) =>
{ if(c ne retractor) c.informAboutNoValue() })
}
}

def connect(c:Constraint) {
constraints = c :: constraints
}
}

//===== Constraints =====
case class Adder(a:Connector,b:Connector,
c:Connector) extends Constraint {
a.connect(this)
b.connect(this)
c.connect(this)

def informAboutValue() {
Tuple(a.hasValue(),b.hasValue(),c.hasValue())
match {
case Tuple3(true,true,false) =>
c.setValue(a.value + b.value, this)
case Tuple3(true,false,true) =>
b.setValue(c.value - a.value, this)
case Tuple3(false,true,true) =>
a.setValue(c.value - b.value, this)
case _ => ; //ignore
}
}

def informAboutNoValue() {
a.forgetValue(this)
b.forgetValue(this)
c.forgetValue(this)
informAboutValue()
}
}

case class Multiplier(a:Connector,b:Connector,
c:Connector) extends Constraint {
a.connect(this)
b.connect(this)
c.connect(this)

def informAboutValue() {
Tuple(a.hasValue(),b.hasValue(),
(a.hasValue() && a.value == 0) ||
(b.hasValue() && b.value == 0),
c.hasValue())
match {
case Tuple4(_,_,true,_) =>
c.setValue(0, this)
case Tuple4(true,true,_,false) =>
c.setValue(a.value * b.value, this)
case Tuple4(true,false,_,true) =>
b.setValue(c.value / a.value, this)
case Tuple4(false,true,_,true) =>
a.setValue(c.value / b.value, this)
case _ => ; //ignore
}
}

def informAboutNoValue() {
a.forgetValue(this)
b.forgetValue(this)
c.forgetValue(this)
informAboutValue()
}
}

case class Constant(value:Int,
c:Connector) extends Constraint {
c.connect(this)
c.setValue(value, this)

def informAboutValue() {
throw new RuntimeException("""|CONSTANT constraint,
| request not allowed
|.""".stripMargin) }
def informAboutNoValue() {
throw new RuntimeException("""|CONSTANT constraint,
| request not allowed
|.""".stripMargin) }
}

case class Probe(c:Connector) extends Constraint {
c.connect(this)

def informAboutValue() {
printProbe(c.value.toString()) }

def informAboutNoValue() {
printProbe("?") }

private def printProbe(value:String) {
println("Probe: " + c.name + " = " + value)
}
}

//====== simulation =====
def celsiusFahrenheitConverter(c:Connector,f:Connector) {
val u = new Connector()
val v = new Connector()
val w = new Connector()
val x = new Connector()
val y = new Connector()

Multiplier(c, w, u)
Multiplier(v, x, u)
Adder(v, y, f)
Constant(9, w)
Constant(5, x)
Constant(32, y)
}

val C = new Connector("Celsius Temp")
Probe(C)
val F = new Connector("Fahrenheit Temp")
Probe(F)

celsiusFahrenheitConverter(C,F)

C.setValue(25, 'user)
//Probe: Fahrenheit Temp = 77
//Probe: Celsius Temp = 25

F.setValue(212, 'user)
//java.lang.RuntimeException: Contradiction 212 77

C.forgetValue('user)
//Probe: Fahrenheit Temp = ?
//Probe: Celsius Temp = ?

F.setValue(212, 'user)
//Probe: Celsius Temp = 100
//Probe: Fahrenheit Temp = 212

No comments:

Post a Comment