Help validating relation in object construction


#1

I’m defining a class BddNode and I’d like to validate a relationship every time I construct a new instance. I have some ugly code which does the trick, but I’d guess there’s a prettier or more idiomatic way. I’d appreciate a suggestion to improve this.

Each time a new object of class BddNode is constructed, two objects (positive and negative) of class Bdd are given. These two given objects may be of class BddNode in which case I want to assert that label < child.label. But when a child is of class BddTerm then no such assertion should be made for that child object.

Later on in the development of the project, I’m going to want to compare such objects often by label. I didn’t want to define <, >, and = methods because I thought it would be confusing (and cause problems) if such object are equal simply because they have the same label. Can I define < and > methods which compare the labels, while maintaining the semantics of = to really mean whether the objects are the same? Will the fact that a<b is false and a>b is false does not imply a=b cause grief later on?

So I guess there are really two questions here:

  1. what is the best way to assert assumptions within the object constructor?
  2. can/should I redefine < and > to have very different semantics than =?
abstract class Bdd (ident:Int) {
}

object Bdd {
  var count = 2
  def nextCount():Int = {
    count += 1
    count - 1
  }
}

case class BddNode(label:Int, positive:Bdd, negative:Bdd) extends Bdd(Bdd.nextCount()) {

  // THIS METHOD IS UGLY
  def validateChild(child:Bdd):Unit= {
    // a BddTerm may be a child of any BddNode
    // but if a BddNode is a child, then the ident of the child must be strictly > ident of parent
    child match {
      case child:BddNode => assert(child.label > label, "expecting child.label > this.label, got "+ child.label +">="+ label)
      case _:BddTerm => Unit
    }
  }
  validateChild(positive)
  validateChild(negative)

  override def toString = {
    "{"+label+"+"+positive+"-"+negative+"}"
  }
}

abstract class BddTerm(ident:Int) extends Bdd(ident) {

}

object BddTrue extends BddTerm(1) {
  override def toString = "T"
}

object BddFalse extends BddTerm(0) {
  override def toString = "F"
}

object BddTest {
  def main(args:Array[String]):Unit = {
    println("true = "+BddTrue)
    println("false= "+BddFalse)
    val bdd1 = BddNode(3,BddTrue,BddFalse)
    val bdd2 = BddNode(2,BddFalse,BddTrue)
    val bdd3 = BddNode(1,bdd1,bdd2)
    println("node1 = "+bdd1)
    println("node2 = "+bdd2)
    println("node3 = "+bdd3)
  }
}```

#2

If you’re ok going with exceptions, I’d think your approach is fine. Perhaps you could move #validateChild() to the companion object to unclutter the actual case class, and Predef#require() might be a better fit than assert.

Alternatively, you could make the constructor private and provide a factory method in the companion object that returns an Either[SomeError, BddNode], SomeError being any type you like to use to signal/explain that the labels were wrong.

Define an Ordering for BddNode based on labels…

object BddNodeLabelOrdering extends Ordering[BddNode] {
  override def compare(x: BddNode, y: BddNode): Int = 
    x.label.compare(y.label)
}

…and use it either explicitly or as an implicit in appropriate scope.

implicit val bddOrd: Ordering[BddNode] = BddNodeLabelOrdering
println(Seq(bdd1, bdd2).sorted) // List({2+F-T}, {3+T-F})

Best regards,
Patrick