Boolean operators for non-boolean types in Scala

I like compressed code that can be written using logical operators, rather than conventions in (usually dynamic) languages ​​such as Lisp, Python, or JavaScript, as in typical:

x = someString or "default string" 

vs

 if someString: x = someString else: x = "default string" 

In Scala, I thought something like:

 object Helpers { case class NonBooleanLogic[A](x: A) { // I could overload the default && and || // but I think new operators are less 'surprise prone' def |||(y: => A)(implicit booleanConversion: A => Boolean) = if (x) x else y def &&&(y: => A)(implicit booleanConversion: A => Boolean) = if (!x) x else y } implicit def num2bool(n : Int) = n != 0 implicit def seq2bool(s : Seq[Any]) = !s.isEmpty implicit def any2bool(x : Any) = x != null implicit def asNonBoolean[T](x: T) = NonBooleanLogic(x) } object SandBox { // some tests cases... 1 ||| 2 //> res2: Int = 1 val x : String = null //> x : String = null x ||| "hello" //> res3: String = hello //works promoting 2 to Float 1.0 &&& 2 //> res4: Double = 2.0 //this doesn't work :( 1 &&& 2.0 } 

But two problems arise:

  • How to make this work for types that have a common ancestor without returning to the Any type?
  • This is so cool that someone else should have done this before, perhaps in a more well-documented, verified and complete library. Where to find him?
+4
source share
3 answers

I will just stick to the [T] option ... which is more idiomatic for Scala. I also often used it in validation, for example: On a web form, sometimes an empty string should not be considered valid user input.

For example, if you think a null String / String with a zero length ( "" ) is false, any null reference is also false, and any numerical zero is false, you can write the following implicit defs.

 object MyOptionConverter { implicit def toOption(any: AnyRef) = Option(any) implicit def toOption(str: String) = { Option(str).filter(_.length > 0) } implicit def toOption[T](value: T)(implicit num: Numeric[T]): Option[T] = { Option(value).filter(_ != 0) } } import MyOptionConverter._ println(1 getOrElse 10) // 1 println(5.5 getOrElse 20) // 5.5 println(0 getOrElse 30) // 30 println(0.0 getOrElse 40) // 40 println((null: String) getOrElse "Hello") // Hello println((null: AnyRef) getOrElse "No object") // No object println("World" getOrElse "Hello") 

And if you really need your own operator, convert it to a class containing the [T] parameter, and add the operator to it.

 object MyOptionConverter { class MyBooleanLogic[T](x: Option[T], origin: T) { def |||(defaultValue: T) = x.getOrElse(defaultValue) def &&&(defaultValue: T) = x.isDefined match { case true => defaultValue case false => origin } } implicit def toOption(any: AnyRef) = { new MyBooleanLogic(Option(any), any) } implicit def toOption(str: String) = { new MyBooleanLogic(Option(str).filter(_.length > 0), str) } implicit def toOption[T](value: T)(implicit num: Numeric[T])= { new MyBooleanLogic(Option(value).filter(_ != 0), value) } } import MyOptionConverter._ println(1 ||| 10) // 1 println(5.5 ||| 20) // 5.5 println(0 ||| 30) // 30 println(0.0 ||| 40) // 40 println((null: String) ||| "Hello") // Hello println((null: AnyRef) ||| "No object") // No object println("World" ||| "Hello") println(1 &&& 10) // 10 println(5.5 &&& 20) // 20 println(0 &&& 30) // 0 println(0.0 &&& 40) // 0.0 println((null: String) &&& "Hello") // null println((null: AnyRef) &&& "No object") // null println("World" &&& "Hello") // Hello 
+3
source

It sounds like you're trying to come up with something like a Monad. What you want to do is already built into the language and distributed in the idiomatic scala. I'm not an expert at Monads, but they say the option is a kind of Monad.

You specifically request the ability to write:

 val x = someString or "default string" 

What makes someString evaluate to false? In most languages, you would test if (someString! = Null) and what you do in your example. Idiomatic scala avoids using null, instead it uses None.

So in the scala syntax you will have

 val someString:Option[String] = getAString() 

or

 val someString:Option[String] = Some("whatever") 

or

 val someString:Option[String] = None 

and then you will have:

 val x = someString getOrElse "default string" 

This is almost what you asked for.

If you want to implement something similar on your own, look at the interface for getOrElse in Option (similar versions exist in Map and other places in the standard library):

 final def getOrElse[B >: A](default: β‡’ B): B 

In this example, the parameter someString is of type (i.e. String) represented by A. B must be A or a supertype A. The return type will be B (which may be A). For instance:

 val x:Option[Int]=1 x getOrElse 1.0 // this will be an AnyVal, not Any. 

AnyVal is the most specific common ancestor of Int and Double. Note that there is AnyVal, not Any.

If you want it to be Double instead of AnyVal, you need x to be the [Double] option (or you need another implicit). There is a built-in implicit conversion from Int to Double, but not from Option [Int] to Option [Double]. Implicit conversion is why your 2 gets promoted to Float, and not because of your logical logic.

I do not think that your operators and the implicits method are the best solution to this problem. There are many ways to write concise, elegant scala code using options, a filter, an existing one, map, flatMap, etc. that can handle the kinds of operations you want to perform.

You may find this helpful:

http://www.codecommit.com/blog/ruby/monads-are-not-metaphors

+2
source

I just created a version for version ||| . This is a show concept. The code can be improved, I was a little hasty.

 // Create a type that does the conversion, C is the resulting type trait Converter[A, B, C] { def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]): C } trait LowerPriorityConverter { // We can convert any type as long as we know how to convert A to a Boolean // The resulting type should be a supertype of both A and B implicit def anyConverter[A <: C, B <: C, C] = new Converter[A, B, C] { def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]) = if (a) a else b } // We can go more specific if we can find a view from B to A implicit def aViewConverter[B <% A, A] = anyConverter[A, A, A] } object Converter extends LowerPriorityConverter { // For Doubles, Floats and Ints we have a specialized conversion as long as the // second type is a Numeric implicit def doubleConverter[A <: Double: Numeric, B: Numeric] = new Converter[A, B, Double] { def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]) = if (a) a else implicitly[Numeric[B]].toDouble(b) } implicit def floatConverter[A <: Float: Numeric, B: Numeric] = new Converter[A, B, Float] { def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]) = if (a) a else implicitly[Numeric[B]].toFloat(b) } implicit def intConverter[A <: Int: Numeric, B: Numeric] = new Converter[A, B, Int] { def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]) = if (a) a else implicitly[Numeric[B]].toInt(b) } } // We have created a typeclass for the boolean converters as well, // this allows us to use more generic types for the converters trait BooleanConverter[A] extends (A => Boolean) trait LowerPriorityBooleanConverter { implicit def any2bool = new BooleanConverter[AnyRef] { def apply(s: AnyRef) = s != null } } object BooleanConverter extends LowerPriorityBooleanConverter { implicit def num2bool[T: Numeric] = new BooleanConverter[T] { def apply(n: T) = implicitly[Numeric[T]].zero != n } // Note that this could catch String as well implicit def seq2bool[T <% GenTraversableOnce[_]] = new BooleanConverter[T] { def apply(s: T) = s != null && !s.isEmpty } } // This is similar to the original post implicit class NonBooleanLogic[A](x: A) { // Note that we let the implicit converter determine the return type // of the method def |||[B, C](y: => B)( // make sure we have implicits for both a converter and a boolean converter implicit converter: Converter[A, B, C], bool: BooleanConverter[A]): C = // do the actual conversion converter.convert(x, y) } 

Result with several tests:

 1 ||| 2 //> res0: Int = 1 (null: String) ||| "test" //> res1: String = test 1.0 ||| 2 //> res2: Double = 1.0 1 ||| 2.0 //> res3: Int = 1 List() ||| Seq("test") //> res4: Seq[String] = List(test) 1f ||| 2.0 //> res5: Float = 1.0 1f ||| 2f //> res6: Float = 1.0 0f ||| 2.0 //> res7: Float = 2.0 0 ||| 2f //> res8: Int = 2 2.0 ||| 2f //> res9: Double = 2.0 2.0 ||| 3.0 //> res10: Double = 2.0 Seq("test") ||| List() //> res11: Seq[String] = List(test) "" ||| "test" //> res12: String = test 

As you can see, to save types we need to use a specific template. I learned this from the answer to one of my own questions here: How to define a method for which the return type is based on argument types and type parameter in Scala?

The surface of this approach is that you can add specific converters for certain types without changing the source code.

+1
source

All Articles