1

I would like to write the following in Scala:

def method(a: Float, b: Float, c: Float) = {
  if( a < b <= c) {
    ...
  }
}

Currently, this is not valid. Indeed, a < b returns a Boolean, which for comparison purposes is wrapped by a booleanWrapper. The compiler then complains that c is of type Float and not Boolean, so it does not compare b to c at all.

Would it be possible to use implicit classes, methods and value classes to be able to achieve this?

For now, I have only been able to do the following:

class Less(val previous: Boolean, last: Float) {
  def <=(other: Float): Less = new Less(previous && last <= other, other)
  def <(other: Float): Less = new Less(previous && last < other, other)
}
implicit def f(v: Float): Less = {
  new Less(true, v)
}
implicit def lessToBoolean(f: Less): Boolean = {
  f.previous
}

def method(a: Float, b: Float, c: Float) = {
  if( f(a) < b <= c) {
    ...
  }
}

Any way to remove this f using standard tricks?

Mikaël Mayer
  • 10,425
  • 6
  • 64
  • 101
  • possible duplicate of [Do macros make naturally chained comparisons possible in Scala?](http://stackoverflow.com/questions/13450324/do-macros-make-naturally-chained-comparisons-possible-in-scala) – senia Jun 20 '13 at 05:21

2 Answers2

3

Yes, you can emulate this with implicits and custom operators. Here is a short example:

implicit class FloatWrapper(n:Float) {
  def :<(that:Float) = ResultAndLastItem(n<that, that)
  def :>(that:Float) = ResultAndLastItem(n>that, that)
  def :=(that:Float) = ResultAndLastItem(n==that, that)
  // insert more comparison operations here
}

case class ResultAndLastItem(result:Boolean, lastItem:Float) extends FloatWrapper(lastItem) {
  // insert boolean operations here
  // boolean operations should return another instance of ResultAndLastItem (?)
}

implicit def toBoolean(r:ResultAndLastItem):Boolean = r.result

Here is an example usage in the REPL:

Welcome to Scala version 2.10.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_45).
Type in expressions to have them evaluated.
Type :help for more information.

scala> :paste
// Entering paste mode (ctrl-D to finish)

implicit class FloatWrapper(n:Float) {
  def :<(that:Float) = ResultAndLastItem(n<that, that)
  def :>(that:Float) = ResultAndLastItem(n>that, that)
  def :=(that:Float) = ResultAndLastItem(n==that, that)
  // insert more comparison operations here
}

case class ResultAndLastItem(result:Boolean, lastItem:Float) extends FloatWrapper(lastItem) {
  // insert boolean operations here
  // boolean operations should return another instance of ResultAndLastItem (?)
}

implicit def toBoolean(r:ResultAndLastItem):Boolean = r.result

// Exiting paste mode, now interpreting.

warning: there were 1 feature warnings; re-run with -feature for details
defined class FloatWrapper
defined class ResultAndLastItem
toBoolean: (r: ResultAndLastItem)Boolean

scala> if(2 :< 3 :< 4) "yay" else "nope"
res0: String = yay

scala> if(2 :< 3 :< 3) "nope" else "yay"
res1: String = yay

Remarks:

  • You can easily add more comparison operators, such as :<=.

  • The usage of custom operators is required because this forces the compiler to use the implicit class above, instead of the built-in default operator for Floats.

  • It is hard to make my example more generic, in order to allow for all comparable types, such as Double, Int, or custom types. The reason why this is hard is because the implicit class FloatWrapper cannot require further implicit arguments - implicits cannot be nested. (Or more precisely, they can, syntactically, but then the compiler won't pick them for implicit resolution.)

  • As an optimization, you might want to consider adding boolean shortcuts, i.e. when an expression is already known to be false, it is not necessary to evaluate the remaining comparisons.

    • This becomes tricky when you also add boolean operators, such as || or &&.
Madoc
  • 5,841
  • 4
  • 25
  • 38
1

This is not possible without explicit wrapping if you use standard types that have methods < and <=. If you however would provide your own types you could do it.

def method(a:SpecialFloat, b:SpecialFloat, c:SpecialFloat)

implicit class SpecialFloat(v:Float) extends AnyVal {

  def < (other:Float) = ??? 
  def <= (other:Float) = ???
}

That way you can implement the methods in any way you like. Calling method(1f, 2f, 3f) would automatically convert them into SpecialFloat instances (more information about the extends AnyVal).

EECOLOR
  • 11,184
  • 3
  • 41
  • 75