Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding arithmetic operators on Int via implicit conversions

Tags:

scala

Say that, for aesthetical reasons, I want to be able to write:

3 / 4

and have / be a method on a class that there exists an implicit conversion from Int to, e.g.:

class Foo(val i: Int) {
  def /(that: Int) = // something
}

implicit def intToFoo(i: Int) = new Foo(i)

Is this at all possible, i.e. is it possible to "disable" the / method on Int?

like image 553
Knut Arne Vedaa Avatar asked Dec 14 '10 20:12

Knut Arne Vedaa


3 Answers

In short: No, you can't.

Implicit resolution will only take place if you attempt to call a method that doesn't already exist.

A more "idiomatic" solution would be to create your own pseudo-number type, something like:

case class Rational(a: Int, b: Int) {
  // other methods
}

val foo = Rational(3, 4)

or

case class Path(value: String) {
  def /(other: String): Path = ...
}

val p = Path("3") / "4"
like image 198
Kevin Wright Avatar answered Nov 11 '22 18:11

Kevin Wright


Is there a reason that something like

trait PathElement[T] { val value: T }
case class IntElement(value: Int) extends PathElement[Int]
case class StringElement(value: String) extends PathElement[String]

case class Path(parts: Seq[PathElement[_]]) {
   def /(other: Path): Path = copy(parts = parts ++ other.parts)
}
object Path {
   def apply(part: PathElement[_]): Path = Path(List(part))
   implicit def int2path(i: Int): Path = Path(IntElement(i))
   implicit def str2path(s: String): Path = Path(StringElement(s))
}

wouldn't work for you? This would allow you to write, for example,

import Path._
"foo" / 3 / 4 / "bar"

This works because String does not have its own / method, so the first "foo" is implicitly converted to a Path. If you were starting a Path with an Int, you'd have to convert it explicitly, but you'd get any other Ints for free.

like image 35
Aaron Novstrup Avatar answered Nov 11 '22 18:11

Aaron Novstrup


I can of course only guess what you really want to accomplish but I assume you don’t just want to match concrete URLs but also extract information from given strings. E.g. when given "/foo/21" you don’t just want to know that this matches some "foo" / 21 but you want to do something with the value of 21.

I’ve found the URI matching process in Lift to be quite useful, so maybe that fits your use case. (I’m using a very simplified version, of course.) It’s done with Lists there which makes matching a little easier but it also means you’ll have to use :: instead of /.

But that’s not the point: what I want to show is the advantage of not using implicit conversions and the power of extractors

object AsInt {
 def unapply(i: String): Option[Int] = try {
    Some(i.toInt)
  } catch {
    case e: java.lang.NumberFormatException => None
  }
}

def matchUrl(url: String) = {
  val req:List[String] = url.split('/').toList.drop(1)
  req match {
    case "foo" :: "bar" :: Nil => println("bar")
    case "foo" :: AsInt(i) :: Nil => println("The square is " + i*i)
    case "foo" :: s :: Nil => println("No int")
    case _ => println("fail")
  }
}

matchUrl("/foo/21")
matchUrl("/foo/b")
matchUrl("/foo/bar")
matchUrl("/foobar")

// prints:
// The square is 441
// No int
// bar
// fail

In short, using the AsInt extractor instead of an implicit conversion of Int to String you can actually retrieve the integer value from the string if and only if it is convertible and of course use it immediately. Obviously, if you don’t like the naming, you can change it to something more unobtrusive but if you really want to do url matching, you maybe should not convert everything implicitly.

like image 26
Debilski Avatar answered Nov 11 '22 16:11

Debilski