Way to reduce the redundancy in following boolean function definitions?
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
ove开发者_开发问答rride 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
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.
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
}
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
}
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))
}
}
精彩评论