tags:

views:

174

answers:

4

Hi,

I am just looking for better Scala ways of doing things, so asking newbie questions. For example:

I wanted to be able to do things like the following:

Given a,b,c which are "boolean":

if (((a nand b) nand c) != (a nand (b nand c))) printf("NAND is not associative")

Where I would cycle through possible a,b,c boolean values. I did that by:

for (i <- 0 to 7) {
  val (a,b,c) = (new MyBoolean((i & 4) >> 2 == 1),
                 new MyBoolean((i & 2) >> 1 == 1),
                 new MyBoolean((i & 1) == 1))
  printf("%d (%s,%s,%s)\n",i,a,b,c)
  if (((a nand b) nand c) != (a nand (b nand c))) printf("NAND\n")
}

I think I could simplify that somewhat to:

val (a,b,c) = (new MyBoolean(i & 4 != 0),
               new MyBoolean(i & 2 != 0),
               new MyBoolean(i & 1 != 0))

Where my MyBoolean class looks like:

class MyBoolean(val p: Boolean) {
  def and(q: MyBoolean): MyBoolean = new MyBoolean(p && q.p)
  override def toString: String = p.toString
  override def equals (o : Any): Boolean = o match {
      case m : MyBoolean => p == m.p
      case _ => false
  }
  def and(q: Boolean): MyBoolean = new MyBoolean(p && q)
  def or(q: Boolean): MyBoolean = new MyBoolean(p || q)
  def or(q: MyBoolean): MyBoolean = or(q.p)
  def negate: MyBoolean = new MyBoolean(!p)
  def nand(q : Boolean): MyBoolean = new MyBoolean(!(p && q))
  def nand(q : MyBoolean): MyBoolean = nand(q.p)
  def nor(q : Boolean): MyBoolean = new MyBoolean(!(p || q))
  def nor(q : MyBoolean): MyBoolean = nor(q.p)
  def xor(q : Boolean): MyBoolean = new MyBoolean((p || q) && !(p && q))
  def xor(q : MyBoolean): MyBoolean = xor(q.p)
  def implies(q : Boolean): MyBoolean = new MyBoolean(!(p && !q))
  def implies(q : MyBoolean): MyBoolean = implies(q.p)
  def equiv(q : Boolean): MyBoolean = new MyBoolean(p == q)
  def equiv(q : MyBoolean): MyBoolean = equiv(q.p)
}

Is this reasonable, or is there a much better way to go about this? :)

I didn't have luck trying things like:

def nand(p : Boolean, q : Boolean): Boolean = !(p && q)

I couldn't go from there to:

(a nand (b nand c))

As I had hoped (against hope). :) I would really rather not introduce a new type, it would be nice to just have nand, nor, etc ... work with Boolean.

I think my main also cries out for making things less redundant, I use different functions on several lines ... but the only thing that changes is the function name.

def main(args: Array[String]): Unit = {
 for (i <- 0 to 7) {
  val (a,b,c) = (new MyBoolean((i & 4) != 0),
                 new MyBoolean((i & 2) != 0),
                 new MyBoolean((i & 1) != 0))
  printf("%d (%s,%s,%s)\n",i,a,b,c)
  if (((a nand b) nand c) != (a nand (b nand c))) printf("NAND\n")
  if (((a implies b) implies c) != (a implies (b implies c))) printf("IMPLIES\n")
  if (((a nor b) nor c) != (a nor (b nor c))) printf("NOR\n")
  if (((a xor b) xor c) != (a xor (b xor c))) printf("XOR\n")
  if (((a equiv b) equiv c) != (a equiv (b equiv c))) printf("EQUIV\n")
 }
}

-Jay

+7  A: 

You're supposed to use implicit conversions for this, like so:

class MyBoolean {/*...*/} 
implicit def decorateBoolean(x:Boolean) = new MyBoolean(x)

for (i <- 0 to 7) {
  val (a,b,c) = ((i & 4) != 0,
                 (i & 2) != 0,
                 (i & 1) != 0)
  printf("%d (%s,%s,%s)\n",i,a,b,c)
  if (((a nand b) nand c) != (a nand (b nand c))) printf("NAND\n")
}

You've now removed the syntactic baggage of object construction from your source code. You just have to hope the JVM suitably optimizes the object construction away when it JITs your code.

Ken Bloom
+3  A: 

As Ken Bloom, I'd suggest to use implicit conversions to save all that typing. It would be convenient to have a conversion in the other direction (MyBoolean -> Boolean) too.

Further it is very, very wasteful to create a new instance when all you have are two distinct values. If you use two objects for these instances, you don't need any references to the original Boolean type any longer.

Here is how I would write it:

sealed trait MyBoolean {
  import MyBoolean._

  def and(that: MyBoolean): MyBoolean = (this, that) match {
    case (TRUE,TRUE) => TRUE
    case _ => FALSE
  }

  def or(that: MyBoolean): MyBoolean = (this, that) match {
    case (FALSE, FALSE) => FALSE
    case _ => TRUE
  }

  def negate: MyBoolean = this != TRUE
  def nand(that: MyBoolean): MyBoolean = (this and that).negate
  def nor(that: MyBoolean): MyBoolean = (this or that).negate
  def xor(that: MyBoolean): MyBoolean = this != that
  def implies(that: MyBoolean): MyBoolean = (this and that.negate).negate
  def equiv(that: MyBoolean): MyBoolean = this == that
}

case object TRUE extends MyBoolean
case object FALSE extends MyBoolean

object MyBoolean {
  implicit def bool2MyBool(p:Boolean):MyBoolean = MyBoolean(p)
  implicit def myBool2bool(m:MyBoolean):Boolean = m == TRUE
  def apply(p:Boolean):MyBoolean = if (p) TRUE else FALSE
}

Note that you don't need equals (because we can rely on object identity now) and toString (because this is already implemented for case objects and classes).

[Edit] An implementation considering Ken's remarks:

sealed trait MyBoolean {
  def and(that: MyBoolean): MyBoolean
  def or(that: MyBoolean): MyBoolean
  def negate: MyBoolean
  def equiv(that: MyBoolean): MyBoolean = if (this == that) TRUE else FALSE
  def nand(that: MyBoolean): MyBoolean = (this and that).negate
  def nor(that: MyBoolean): MyBoolean = (this or that).negate
  def xor(that: MyBoolean): MyBoolean = (this equiv that).negate
  def implies(that: MyBoolean): MyBoolean = (this and that.negate).negate
}

case object TRUE extends MyBoolean {
  def negate = FALSE
  def and(that:MyBoolean) = that
  def or(that:MyBoolean) = this
}
case object FALSE extends MyBoolean {
  def negate = TRUE
  def and(that:MyBoolean) = this
  def or(that:MyBoolean) = that
}

object MyBoolean {
  implicit def bool2MyBool(p:Boolean):MyBoolean = MyBoolean(p)
  implicit def myBool2bool(m:MyBoolean):Boolean = m == TRUE
  def apply(p:Boolean):MyBoolean = if (p) TRUE else FALSE
}
Landei
Rex Kerr
Yes, that's faster. For a real world implementation probably all methods should be written that way (needing implicit conversions for negate etc isn't nice either).
Landei
+1  A: 

This is an improvement of Landei's answer, based on how Ruby does it:

sealed trait MyBoolean {
  val toBoolean:Boolean
  def and(that: MyBoolean): MyBoolean
  def or(that: MyBoolean): MyBoolean
  def negate: MyBoolean
  def nand(that: MyBoolean): MyBoolean
  def nor(that: MyBoolean): MyBoolean
  def xor(that: MyBoolean): MyBoolean
  def implies(that: MyBoolean): MyBoolean
  def equiv(that: MyBoolean): MyBoolean
}

case object TRUE extends MyBoolean{
   override val toBoolean = true
   override def and(that:MyBoolean) = that
   override def or(that:MyBoolean) = TRUE
   override def negate: MyBoolean = FALSE
   override def nand(that:MyBoolean) = that.negate
   override def nor(that:MyBoolean) = FALSE
   override def xor(that:MyBoolean) = that.negate
   override def implies(that:MyBoolean) = that
   override def equiv(that:MyBoolean) = that
}

case object FALSE extends MyBoolean{
   override val toBoolean = false
   override def and(that:MyBoolean) = FALSE
   override def or(that:MyBoolean) = that
   override def negate: MyBoolean = TRUE
   override def nand(that:MyBoolean) = TRUE
   override def nor(that:MyBoolean) = that.negate
   override def xor(that:MyBoolean) = that
   override def implies(that:MyBoolean) = TRUE
   override def equiv(that:MyBoolean) = that.negate
}

object MyBoolean {
  implicit def bool2MyBool(p:Boolean):MyBoolean = MyBoolean(p)
  implicit def myBool2bool(m:MyBoolean):Boolean = m.toBoolean
  def apply(p:Boolean):MyBoolean = if (p) TRUE else FALSE
}
Ken Bloom
A: 

I need to learn how the data type conversions work, and really the details of traits. What I am doing at the moment (with code VERY similar to what I found http://scalasolutions.wordpress.com/2010/07/22/p46-truth-tables-for-logical-expressions/):

object Logic {
  type T = Boolean

  def and(p : T, q : T) = p && q
  def or(p : T, q : T) = p || q
  def negate(p : T) = !p
  def nand(p : T, q : T) = !(p && q)
  def nor(p : T, q : T) = !(p || q)
  def xor(p : T, q : T) = p != q
  def implies(p : T, q : T) = !(p && !q)
  def equiv(p : T, q : T) = p == q
}

def main(args: Array[String]): Unit = {
  import Logic._
  type T = ((Boolean, Boolean) => Boolean, String)
  val checklist =
    List((nand, "NAND\n") : T, (implies, "IMPLIES\n") : T, (nor, "NOR\n") : T,
   (xor, "XOR\n") : T, (equiv, "EQUIV\n") : T)
  // Check associativity of boolean operators does a+(b+c) = (a+b)+c ?
  for (i <- 0 to 7) {
    val (a,b,c) = ((i & 4) != 0, (i & 2) != 0, (i & 1) != 0)
    printf("%d (%s,%s,%s)\n",i,a,b,c)
    checklist.foreach(t => if (t._1(t._1(a,b),c) != t._1(a,t._1(b,c))) printf(t._2))
  }
}
jayped007