2

I use scala f string interpolator as follows:

def format(id: Int) = f"A$id%04d"
format(21)  // A0021

However, I would like to be able to define a length once and for all (before fixed to 4), and get a function that it is going to format the string with that length. So, instead of having

def format(length: Int, id: Int) = ???
f(5, 21)       // A00021

I would like to have this:

def format(length: Int)(id: Int) = ???
val f = format(5)
f(21)       // A00021

How can I implement this using scala f interpolator or other?

Update

I was not looking for such a solution involving the compiler at runtime, but I appreciate som-snytt's answer. Here there is a working solution based on his answer:

import scala.tools.reflect._,scala.reflect.runtime._,universe._

def defFormat(length: Int): Int => String = {
  val code = raw"""(i: Int) => f"A$$i%0${length}d""""
  tb.eval(tb.parse(code)).asInstanceOf[Int => String]
}

val format = defFormat(length = 5)

format(21)
David Portabella
  • 12,390
  • 27
  • 101
  • 182
  • I think that this might help you: http://stackoverflow.com/questions/13260864/string-interpolation-in-scala-2-10-how-to-interpolate-a-string-variable – Michael Dec 07 '16 at 09:43

2 Answers2

6
scala> def format(n: Int)(i: Int) =
     | f"A%%0${n}d" format i
format: (n: Int)(i: Int)String

scala> format(5) _
res0: Int => String = <function1>

scala> .apply(21)
res1: String = A00021

Edit:

scala> import scala.tools.reflect._,scala.reflect.runtime._,universe._
import scala.tools.reflect._
import scala.reflect.runtime._
import universe._

scala> val tb = currentMirror.mkToolBox()
tb: scala.tools.reflect.ToolBox[reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@2d10e0b1

scala> def f(n: Int)(i: Int): String = {
     |   val code = raw"""f"A$${$i}%0${n}d""""
     |   tb.eval(tb.parse(code)).asInstanceOf[String]
     | }
f: (n: Int)(i: Int)String

scala> val g = f(5) _
g: Int => String = <function1>

scala> g(21)
res9: String = A00021

That doesn't actually help much. You really want to

scala> tb.typecheck(tb.parse(code))
scala.tools.reflect.ToolBoxError: reflective typecheck has failed: illegal conversion character 'k'
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$typecheck$1.apply(ToolBoxFactory.scala:178)

which throws if the format is bad.

scala>   val code = raw"""(i: Int) => f"A$${i}%k0${10}d""""
code: String = (i: Int) => f"A${i}%k010d"

scala> tb.typecheck(tb.parse(code))
scala.tools.reflect.ToolBoxError: reflective typecheck has failed: illegal conversion character 'k'
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$typecheck$1.apply(ToolBoxFactory.scala:178)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$typecheck$1.apply(ToolBoxFactory.scala:170)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1$$anonfun$11.apply(ToolBoxFactory.scala:148)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1$$anonfun$11.apply(ToolBoxFactory.scala:148)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1$$anonfun$9.apply(ToolBoxFactory.scala:138)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1$$anonfun$9.apply(ToolBoxFactory.scala:138)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1$$anonfun$withContext$1$1.apply(ToolBoxFactory.scala:139)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1$$anonfun$withContext$1$1.apply(ToolBoxFactory.scala:139)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1$$anonfun$7.apply(ToolBoxFactory.scala:137)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1$$anonfun$7.apply(ToolBoxFactory.scala:137)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1.apply(ToolBoxFactory.scala:148)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$transformDuringTyper$1.apply(ToolBoxFactory.scala:121)
  at scala.reflect.internal.Trees$class.wrappingIntoTerm(Trees.scala:1716)
  at scala.reflect.internal.SymbolTable.wrappingIntoTerm(SymbolTable.scala:16)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.withWrapping$1(ToolBoxFactory.scala:120)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.transformDuringTyper(ToolBoxFactory.scala:121)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.typecheck(ToolBoxFactory.scala:169)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$$anonfun$typecheck$2.apply(ToolBoxFactory.scala:375)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$$anonfun$typecheck$2.apply(ToolBoxFactory.scala:367)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.liftedTree2$1(ToolBoxFactory.scala:355)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.apply(ToolBoxFactory.scala:355)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.typecheck(ToolBoxFactory.scala:367)
  at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.typecheck(ToolBoxFactory.scala:27)
  ... 32 elided

scala>   val code = raw"""(i: Int) => f"A$${i}%0${10}d""""
code: String = (i: Int) => f"A${i}%010d"

scala> tb.typecheck(tb.parse(code))
res19: tb.u.Tree =
((i: Int) => ({
  val arg$macro$9: Int = i;
  new scala.collection.immutable.StringOps("A%010d").format(arg$macro$9)
}: String))
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • 1
    yes, but here you are using the format method, not the f string interpolator – David Portabella Dec 07 '16 at 14:21
  • You asked for "f interpolator or other." I'll add toolbox staging in case it helps. – som-snytt Dec 07 '16 at 22:00
  • So you build the function to check the format, then you build the code to eval. That's as opposed to generating and loading classes with the full compiler, in which case you would just emit the function to use. – som-snytt Dec 07 '16 at 22:15
1

You can't do it using f because its whole point is to make sure it can check the format string for type errors, so the format string has to be static. f could support this scenario explicitly, but it doesn't.

You could make format a macro, but this seems like an overkill. Not to mention that it would have to be defined in a separate module, which looks very inconvenient for this scenario.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487