Overriding arithmetic operators on Int via implici

2020-02-06 09:43发布

问题:

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?

回答1:

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"


回答2:

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.



回答3:

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.



标签: scala