1

Inspired by this, I was wondering if we can have type-safe string interpolations in Scala (maybe using macros)?

For example, I want to have something like this

def a[A] = ???
val greetFormat = f"Hi! My name is ${a[String]}. I am ${a[Int]} years old" 
greetFormat.format("Rick", 27)  // compiles
//greetFormat.format("Rick", false)  // does not compile
//greetFormat.format(27, "Rick") // does not compile
//greetFormat.format("Rick", 27, false) // does not compile
//greetFormat.format("Rick") // does not compile or is curried?
pathikrit
  • 32,469
  • 37
  • 142
  • 221

3 Answers3

3

The f string interpolator is already implemented with a macro.

This can be demonstrated inside of the REPL:

scala> val b = "not a number"
b: String = not a number

scala> f"$b%02d"
<console>:9: error: type mismatch;
 found   : String
 required: Int
              f"$b%02d"
                 ^
wingedsubmariner
  • 13,350
  • 1
  • 27
  • 52
  • I see. But, it forces me to supply formatters for it to trigger the type check fail e.g. if I just do `f"$b"` and `b` is an `Int` it will still work. I want something like `f"${a[String]}"` – pathikrit May 06 '14 at 21:25
  • 5
    @wrick That really has nothing to do with type-safe string interpolation - you want to make sure variables conform to types that aren't even required by the string interpolation. In any case, what you want can be easily done with `f"${a: String}"` – wingedsubmariner May 06 '14 at 21:48
  • 1
    `f"$b"` is just shorthand for `f"$b%s"` – som-snytt May 07 '14 at 04:49
2

Just wrap it in a function.

def greet(name: String, age: Int) = s"Hi! My name is $name.  I am $age years old"
Ryan
  • 7,227
  • 5
  • 29
  • 40
  • 1
    Probably a better design in most cases but does not answer OPs question about how to do it in a *generic* fashion – pathikrit May 06 '14 at 21:23
1

You can supply implicits to the f-interpolator:

scala> case class A(i: Int)
defined class A

scala> implicit def atoi(a: A): Int = a.i
warning: there were 1 feature warning(s); re-run with -feature for details
atoi: (a: A)Int

scala> f"${A(42)}%02d"
res5: String = 42

See also Travis Brown's examples and solution for using regex group names in extractions. It took me about a minute to steal that great idea.

"a123bc" match {
  case res @ xr"(?<c>a)(?<n>\d+)(?<s>bc)" => assert {
    res.c == 'a' && res.n == 123 && res.s == "bc"
  }
}

For the record, on the composition side, I would like:

val a = A(Rick, 42)
val greeter = f"Hi! My name is $_. I am ${_}%d years old" 
greeter(a, a)

But it was deemed too much for the poor underscore. You'll have to write the function as in the other answer.

Your form, in which your macro sees "${a[Int]}" and writes a function with an Int param, doesn't look hard to implement.

Other features of the f-interpolator include other static error checking:

scala> f"$b%.02d"
<console>:19: error: precision not allowed
              f"$b%.02d"
                   ^

and support for Formattable:

scala> val ff = new Formattable { def formatTo(fmtr: Formatter, flags: Int, width: Int, precision: Int) = fmtr.format("%s","hello, world") }
ff: java.util.Formattable = $anon$1@d2e6b0b

scala> f"$ff"
res6: String = hello, world

A quick macro might emit (i: Int) => f"${ new Formattable {...} }".

som-snytt
  • 39,429
  • 2
  • 47
  • 129