【问题标题】:Overriding arithmetic operators on Int via implicit conversions通过隐式转换覆盖 Int 上的算术运算符
【发布时间】:2010-12-15 08:46:22
【问题描述】:

说,出于审美原因,我希望能够写作:

3 / 4

并且让/成为一个类的方法,该类存在从 Int 到的隐式转换,例如:

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

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

这是否可能,即是否可以“禁用” Int 上的 / 方法?

【问题讨论】:

  • 没有足够的 Scala 专家来告诉你是否可以这样做,但我可以告诉你不应该这样做。除非您尝试构建“Scala Puzzlers”之类的东西,否则您只是以极低的折扣购买了很多麻烦。
  • 为了提供一些上下文,我想做的是编写一个 URL 匹配器,您可以在其中指定 URL 模式,例如“foo/bar”/34/“一些/东西”。显然我可以你\或其他任何东西,但“foo/bar”\34\“some/thing”看起来有点奇怪。我认为在这种情况下,任何代码读者都应该非常清楚 / 的用法。
  • 请注意,在您的评论示例中,隐式转换是 String 到 Foo,而不是 Int
  • 两者。 (即某些并行类。)请注意,为简洁起见,已对问题中给出的示例进行了简化,并且并未完全实现评论中概述的应用程序。 :)

标签: scala


【解决方案1】:

简而言之:不,你不能。

仅当您尝试调用尚不存在的方法时才会进行隐式解析。

更“惯用”的解决方案是创建自己的伪数字类型,例如:

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

val foo = Rational(3, 4)

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

val p = Path("3") / "4"

【讨论】:

  • 我想从字面上写3 / 4,但是/的意思并不是要数字除法,而是路径除法。 (见对问题的评论。)
  • 然后创建一个新类,以 3 作为构造函数参数,并在 that 上定义 /
  • 还不如只做“3”/“4”。再次,美学问题。重点是让它成为一个干净的 DSL,尽量减少混乱。最简单的可能是例如"foo" / n(3) / n(4) 等具有从案例类 n(i: Int) 到一般项目类的隐式转换。在我看来仍然很丑。再说一遍,在 URL 模式中指定字面量整数在实践中可能被证明是毫无用处的(即为什么不直接使用“foo/bar/3/4/some/thing”),所以这个问题可能只有学术兴趣。 :)
【解决方案2】:

有类似的原因吗

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))
}

不适合你吗?例如,这将允许您编写

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

这是可行的,因为 String 没有自己的 / 方法,因此第一个 "foo" 被隐式转换为 Path。如果您以Int 开头Path,则必须显式转换它,但您将免费获得任何其他Ints。

【讨论】:

  • 这是一个很好的观点,我没有真正考虑过,只有当 Int 是链中的第一个时才会出现问题。解决了大多数实际用途的问题。 (假设有。):)
【解决方案3】:

我当然只能猜测您真正想要完成什么,但我假设您不仅想匹配具体的 URL,还想从给定的字符串中提取信息。例如。当给定 "/foo/21" 时,您不仅想知道这与某些 "foo" / 21 匹配,而且您还想做一些值为 21 的事情。

我发现 Lift 中的 URI 匹配过程非常有用,所以可能适合您的用例。 (当然,我使用的是一个非常简化的版本。)它是用 Lists 完成的,这使得匹配更容易一些,但这也意味着你必须使用 :: 而不是 /

但这不是重点:我想展示的是 not 使用隐式转换的优势和提取器的强大功能

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

简而言之,使用AsInt 提取器而不是将Int 隐式转换为String,当且仅当它是可转换的并且当然可以立即使用它时,您实际上可以从字符串中检索整数值。显然,如果你不喜欢这个命名,你可以把它改成更不显眼的东西,但如果你真的想做 url 匹配,你可能不应该隐式转换所有内容。

【讨论】:

  • 实际上这并不是我想要做的,而是以最简单的方式指定 url 模式,就像 Scalatra 但使用类型化的元素而不是将所有内容指定为字符串。现在,这实现起来相对微不足道,我只是被“Int 上的 / 方法”部分难住了。
猜你喜欢
  • 1970-01-01
  • 2017-03-23
  • 1970-01-01
  • 2016-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-25
相关资源
最近更新 更多