3

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

sam boosalis
  • 1,997
  • 4
  • 20
  • 32
  • 1
    I may be wrong, but I doubt it can be achieved. The reason is that `1f` and `1d` are considered numbers as well and it seems like Scala cannot figure out where the number part ends and the method part starts. Your approach would work fine with `String`s: `"1"px` would work great – serejja Apr 15 '14 at 18:49
  • 1
    The parser could desugar `1px` much as it does `s""`, with a `px` extension method on a special type instead of Int. – som-snytt Apr 16 '14 at 06:31
  • @som-snytt can you explain? – sam boosalis Apr 16 '14 at 07:23

2 Answers2

3

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.
Yuriy
  • 2,772
  • 15
  • 22
  • thanks for the answer, but as i said, it needs to be type-safe. otherwise, why use scala, right? – sam boosalis Apr 18 '14 at 02:55
  • you might be able to make it type-safe, and have a better syntax, by using a different StringContext for each unit (i.e. not u"10px" but px"10"). as long as you don't clash with the builtins. – sam boosalis Apr 18 '14 at 02:56
1

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? .

Community
  • 1
  • 1
elm
  • 20,117
  • 14
  • 67
  • 113
  • 1
    thanks for answering, but i had already proposed this problematic solution in my question, which states that i want 1. type safety and 2. better syntax. – sam boosalis Apr 15 '14 at 20:08