Enjoying Scala's Compactness

As I slowly learn my way around the Scala APIs, I'm enjoying the expressiveness that is possible. You can do a lot with just a little bit of code.

Operating on Files

For example, I needed a way to perform an operation on every file in a directory tree.

 * Call proc(f) for each file in the directory tree rooted at dir.
 * proc will not be called for directories, just for files.
def traverse(dir: File, proc: File => Unit): Unit = {
    dir.listFiles foreach { f => if(f.isDirectory) traverse(f, proc)
                                 else proc(f) }

That File is just a java.io.File, and its listFiles method returns an Array of File.

Scala's foreach calls the given anonymous function for each element in the array.

Unit is Scala's name for void.

Operating on Strings, Experimenting in the REPL

Related to files, but operating on strings, I needed to take a file, say content/topic/subtopic/post.md and generate the name where the output would land, in this case output/topic/subtopic/post.html.

This means splitting the filename by "/", dropping the first directory, prepending the output directory, and changing the expression from ".md" to ".html".

I had to experiment in the REPL (along with heavy use of ddg) to figure this one out, but it ends up being a (long) one-liner.

scala> val f = new java.io.File("content/topic/subtopic/post.md")
f: java.io.File = content/topic/subtopic/post.md

First, the File object gives us the parent directory of the file, and splitting on the separator gives an array of Strings representing the path.

scala> f.getParent split java.io.File.separator
res37: Array[java.lang.String] = Array(content, topic, subtopic)

I want to drop the first directory component:

scala> f.getParent split java.io.File.separator drop 1
res38: Array[java.lang.String] = Array(topic, subtopic)

and put the output directory at the front of the list:

scala> val out = new java.io.File("output")
out: java.io.File = output

scala> out.getPath :: ((f.getParent split java.io.File.separator).toList drop 1)
res42: List[java.lang.String] = List(output, topic, subtopic)

[If you're paying close attention to the REPL output, you'll notice that I'm leaving out my mistakes, i.e. res39 through res41.]

I had to convert the Array returned by split to a list, drop the first element of the list, and then prepend the output directory using Scala List :: function.

Now I can join the strings in the list together with a slash to get the output directory that the file belongs in:

scala> out.getPath :: ((f.getParent split java.io.File.separator).toList drop 1) mkString java.io.File.separator
res44: String = output/topic/subtopic

That java.io.File is kind of a hassle to keep typing out:

scala> import java.io.File
import java.io.File

scala> out.getPath :: ((f.getParent split File.separator).toList drop 1) mkString File.separator
res45: String = output/topic/subtopic

That's a little better.

Now let's take care of the filename, which is a trivial replacement:

scala> f.getName.replaceAll(".md", ".html")
res46: java.lang.String = post.html

and append it to the path we just generated:

cala> out.getPath :: ((f.getParent split File.separator).toList drop 1) mkString File.separator + f.getName.replaceAll(".md", ".html")
res47: String = output/post.htmltopic/post.htmlsubtopic

Whoops, that's not right -- it needs parentheses so that the mkString happens first, and an extra path separator:

scala> (out.getPath :: ((f.getParent split File.separator).toList drop 1) mkString File.separator) + File.separator + f.getName.replaceAll(".md", ".html")
res49: java.lang.String = output/topic/subtopic/post.html

So the function that I added to my class is (note that the out from above is called outDir here):

def outputName(f: File): String = {
    val sep = File.separator
    (outDir.getPath ::
        ((f.getParent split sep).toList drop 1) mkString sep) +
        sep + f.getName.replaceAll(".md", ".html")

Doesn't exactly look like a one-liner, but it could be if I didn't break it up for readability.

Posted on 2013-12-11 by brian in scala .
Comments on this post are closed. If you have something to share, please send me email.