Open string for output

Is there an approximation of the CL macro with-output-to-string in scala?
The macro provides a writable stream (sort of a open file port) which the macro body can write to, and the evaluation of the macro is the string containing all the characters written to the stream.

http://clhs.lisp.se/Body/m_w_out_.htm

Actually in Scala there’s no reason for the construct to be a macro, a normal 1st order function would work just fine. A function which I could call as follows:

callWithOutputToFile(port:File => {
  port.write("hello ")
  port.write("world")
})

Such a call would return the string "hello world".

No, but you can easily write a function to do this using ByteArrayOutputStream:

def printToOutputString(f: PrintWriter => Unit): String = {
  val baos = new ByteArrayOutputStream()
  val pw = new PrintWriter(baos)
  try {
    f(pw)
    new String(baos.toByteArray, StandardCharsets.UTF_8)
  } finally pw.close()
}

val helloWorld = printToOutputString { pw =>
  pw.print("hello ")
  pw.print("world")
}
1 Like

Thanks David for this example. I think I’ll use it often in the future. In Common Lisp this pattern is very useful, in particular for print-object methods, the Scala approximation of toString. It is sometimes desirable for the print-object method to print part of its result, and then do some calculation before printing other parts.

However, this pattern should also be useful, now that I have an example, for several types of accumulators. For example collectors, summers, or maximizers. Imagine the following, which should return the maximum value which is passed to the given max function.

val themax = maximize { m =>
   if (something) m(a)
   if (something-else) m(b)
   do-some-calcualtion()
   if (some-third-thing) m(c)
   data.foreach{ m( f(data)) }
}

Reminds me a little bit of this!

lambda is the ultimate imperative. :stuck_out_tongue_winking_eye:

1 Like

@DavidGregory084, I’m just now testing out your suggestion. It doesn’t work for me. The value of helloWorld is "" empty string at the end. Is something missing in your code example?
By the way, I wasn’t sure what to import, so I imported the following:

Also linked here to ScalaFiddle

    import java.io.{ByteArrayOutputStream,PrintWriter}
    import java.nio.charset.StandardCharsets


    def printToOutputString(f: PrintWriter => Unit): String = {
      val baos = new ByteArrayOutputStream()
      val pw = new PrintWriter(baos)
      try {
        f(pw)
        new String(baos.toByteArray, StandardCharsets.UTF_8)
      } finally pw.close()
    }

    val helloWorld = printToOutputString { pw =>
      pw.print("hello ")
      pw.print("world")
    }
    println(s"helloWorld=$helloWorld")
  }

If you can suggest a fix, that’d be great.

PrintWriter is buffering. When you create the string, the data probably hasn’t been pushed to the ByteArrayOutputStream, yet. Inject a pw.flush() before accessing the baos bytes.

2 Likes

@sangamon, I’ve updated the ScalaFiddle with your suggested fix. It seems to work.

Alternatively you could pull string creation outside the PrintWriter scope - closing the writer will implicitly flush, too.

  def printToOutputString(f: PrintWriter => Unit): String = {
    val baos = new ByteArrayOutputStream()
    val pw = new PrintWriter(baos)
    try f(pw) finally pw.close()
    new String(baos.toByteArray, StandardCharsets.UTF_8)
  }
2 Likes