I listened to the continuing conversation and realised that the solution being discussed - which involved seeking through the string, remembering where the last whitespace was and cutting off substrings at appropriate points - was obviously imperative in nature. "Not that there's anything wrong with that." If I wasn't reading a book about Scala programming at the moment I would have joined right in. But my functionally-leaning mind instantly started to wonder what the functional solution would be.
It sounded like a simple but interesting problem to try and solve with a new language - a nice "real" program to attempt after Hello World. So I sat down after work and whipped up a small Scala program to wrap words. I have to admit, it took a bit longer than I expected - over an hour. Though a large part of that was spent looking up docs for the List class and trying to solve new and bemusing syntax errors, I do recall getting distracted by an investigation into when tail call optimisation does and doesn't occur.
If you're new to Scala and looking for a little problem to help you learn some syntax and waste an hour, I can recommend word-wrapping as a suitable foe. My solution is below. If you come up with your own, I'd love to know what you did differently!
object Wrapper {
type Line = List[String]
def main(args: Array[String]) {
for (i <- 1 to 31) print(if (i == 31) '\n' else '.')
val s = "This is a really, really long line of text that, hopefully, " +
"will be long enough to sufficiently test a function that must " +
"divide a really, really long string into lines that are no " +
"more than 30 (that's thirty) characters long."
for (line <- wrap(s)) {
for (string <- line) {
print(string + " ")
}
println
}
}
def wrap(stringToWrap : String) : List[Line] =
wrap(List.fromArray(stringToWrap.split(" ")), 30, List(List()))
def wrap(stringsToWrap : List[String],
maxLineLength : Int,
lines : List[Line]) : List[Line] =
stringsToWrap match {
case Nil => lines.reverse
case nextString :: remainingStrings =>
if (lineLength(lines.head) + nextString.length <= maxLineLength)
wrap(remainingStrings, maxLineLength,
(lines.head ::: List(nextString)) :: lines.tail)
else {
wrap(remainingStrings, maxLineLength, List(nextString) :: lines)
}
}
def lineLength(s : Line) : Int = s match {
case Nil => 0
case head :: tail => 1 + head.length + lineLength(tail)
}
}