2

I would like to pretty-print a Product, such as a case class, so I create the following trait:

  trait X extends Product {
    def fmtStrs =
      productIterator map {
        case _ : Double => "%8.2f"
        case _ => "%4s"
      } map (_ + separator) toSeq
    override def toString = {
      new StringContext("" +: fmtStrs : _*) f (productIterator.toSeq : _*)
    }
  }

This uses string interpolation as described in the ScalaDoc for StringContext.

But this won't compile, with this cryptic error:

Error:(69, 70) too many arguments for interpolated string
      new StringContext("" +: fmtStrs : _*) f (productIterator.toSeq : _*)

Is this a bug, or limitation of a macro? Note that doing the following works fine, so I suspect this may be related to the variable argument list:

scala> val str2 = StringContext("","%4s,","%8.2f").f(1,23.4)
str2: String = "   1,   23.40"
1
  • Rather bizarrely, if you change the f interpolator to the s interpolator, this appears to complie (and work) ... Commented Nov 3, 2016 at 15:19

1 Answer 1

2

The reason f is a macro is so that it can give you an error when types of format specifiers and arguments don't match, and this isn't possible to check by looking at ("" +: fmtStrs : _*) and (productIterator.toSeq : _*), so it isn't particularly surprising this doesn't work. The error message could be clearer, so let's see what exactly happens.

If you look at the implementation of f (it took me some time to actually find it, I finally did by searching for the error message), you'll see

c.macroApplication match {
  //case q"$_(..$parts).f(..$args)" => 
  case Applied(Select(Apply(_, parts), _), _, argss) => 
    val args = argss.flatten
    def badlyInvoked = (parts.length != args.length + 1) && truly {
      def because(s: String) = s"too $s arguments for interpolated string"
      val (p, msg) =
        if (parts.length == 0) (c.prefix.tree.pos, "there are no parts")
        else if (args.length + 1 < parts.length)
          (if (args.isEmpty) c.enclosingPosition else args.last.pos, because("few"))
        else (args(parts.length-1).pos, because("many"))
      c.abort(p, msg)
    }
    if (badlyInvoked) c.macroApplication else interpolated(parts, args)

With your call you have a single tree in both parts and argss, and parts.length != args.length + 1 is true, so badlyInvoked is true.

s doesn't care what its arguments look like, so it's just a method and your scenario works.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks; for the record, just ended up calling java.lang.String.Formatter directly instead of using string interpolation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.