sam boosalis
sam boosalis

Reputation: 1977

Scala: "number" interpolation

Scala has string interpolation like raw"\n" for raw strings.

Does it have anything like number interpolation e.g. 1px for one pixel? A nice syntax for numeric units would both make code more readable and make it easier to write safer code.

Like strings, numbers have a nice literal syntax and are fundamental.

prefix notation px(1) is not how people write units:

case class px(n: Int)

And I don't think a postfix notation via implicit conversion can work:

case class Pixels(n: Int) {
 def px() = Pixels(n)
 def +(p: Pixels) = p match {case Pixels(m) => Pixels(n+m)}
}
implicit def Int2Pixels(n: Int) = Pixels(n)
  1. it needs a dot or space or parens (i.e. not (1 px) or (1)px or 1.px, which is not how humans write units).

  2. it won't check types i.e. we want to explicitly cast between these numeric type-alias things and numbers themselves (i.e. 1.px + 2 and 1 + 2.px and def inc(p: Pixels) = p + Pixels(1) with inc(0) all don't fail, because of the implicit cast, when they should).

Upvotes: 3

Views: 291

Answers (2)

Yuriy
Yuriy

Reputation: 2769

You can define own string interpolation (more details here):

implicit class UnitHelper(val sc : StringContext) extends AnyVal {
  def u(args: Any*): Pixels = {
    val pxR = """(\d.*)\s*px""".r   
    sc.parts.mkString match {
      case pxR(px) => Pixels(px.toInt)
      case _ => throw new IllegalArgumentException
    }
  }
} 

Usage example:

val x = u"10px" + u"20px" // x = Pixels(30)

Pros:

  • easy to add interpolation for any units: em, px, pt, cm, in

Cons:

  • it is not type safe because string interpolation is runtime feature.

Upvotes: 3

elm
elm

Reputation: 20415

Try the following implicit,

implicit def int2Pixels(n: Int) = new {
  def px = Pixels(n)
}

Then

1 px
res: Pixels = Pixels(1)

Here we invoke the px method on object 1, a method that is not defined in Int and so the implicit takes a role.

Altogether,

case class Pixels(n: Int) {
  def px() = Pixels(n)
  def +(p: Pixels) = p match {case Pixels(m) => Pixels(n+m)}
  def +(m: Int) = Pixels(n+m)
}

// for enabling implicit conversion
import scala.language.implicitConversions 
implicit def int2Pixels(n: Int) = new {
  def px = Pixels(n)
}

and so

(12 px) + 1
res: Pixels = Pixels(13)

(12 px) + 1 px
res3: Pixels = Pixels(13)

For explanation on import see for instance Why implicitConversions is required for implicit defs but not classes? .

Upvotes: 1

Related Questions