I've already written several dozens of scala scripts (series computations, ssh automation, various jmx operations, remote administration, garbage collector log analysis, ...) and found this language quite interesting for use as a script language. There's many reasons for that :
  • Script automatic compilation reduce runtime error. I am often amazed at the first attempt to get a script that works and without any runtime error !
  • Take benefits of scala powerfull collections that make possible to write "sql" like operations
  • It becomes straightforward to parallelize tasks using Actors; one of my favorite use case is a short script that trigger an explicit garbage collection on several dozens of remote jvm in a very short time
But I miss some features that will help to make scala scripts even simpler and concise :
  • A #include like feature within script
  • A way to modify default imports, to avoid adding always the same imports in all scripts
  • the #! !# shell scala bootstrap can become long (and not DRY) once you want to add many external java dependencies
In fact those missing features are no so difficult to implement, the following source code is a proof of concept that shows it no so difficult to implement those features. It defines a class, Bootstrap, which can be use to start a scala script and that will bring new imports and definitions to your script.

package fr.janalyse.script

import scala.tools.nsc.ScriptRunner
import scala.tools.nsc.GenericRunnerCommand
import scala.io.Source

object Bootstrap {

val header =
"""// WARNING
// Automatically generated file - do not edit !
import sys.process.Process
import sys.process.ProcessBuilder._

case class CurDir(cwd:java.io.File)
implicit def stringToCurDir(d:String) = CurDir(new java.io.File(d))
implicit def stringToProcess(cmd: String)(implicit curDir:CurDir) = Process(cmd, curDir.cwd)
implicit def stringSeqToProcess(cmd:Seq[String])(implicit curDir:CurDir) = Process(cmd, curDir.cwd)

implicit var cwd:CurDir=scala.util.Properties.userDir
def cd(dir:String=util.Properties.userDir) = cwd=dir

"""

val footer =
"""
"""

def main(cmdargs:Array[String]) {

def f(name:String) = new java.io.File(name)

val na = List("-nocompdaemon","-usejavacp","-savecompiled", "-deprecation") ++ cmdargs.toList

val command = new GenericRunnerCommand(na)

import command.settings

val scriptname = command.thingToRun
val script = f(scriptname)
val richerScript = f(scriptname.replaceFirst(".scala", ".scala-plus"))

if (script.exists()) {
if (!richerScript.exists || (script.lastModified > richerScript.lastModified)) {
val content=Source.fromFile(script).getLines().toList
val cleanedContent = content.dropWhile(x => !x.startsWith("!#")).tail.mkString("\n")
val newcontent = List(header, cleanedContent, footer).mkString("\n")
new java.io.FileOutputStream(richerScript) {
write(newcontent.getBytes())
}.close()
}
}

val args = command.arguments

ScriptRunner.runScript(settings, richerScript.getName, args)
}
}

Then generate a standalone executable jar with this class and all needed dependencies, thanks to such SBT build specification :

import AssemblyKeys._

seq(assemblySettings: _*)

name := "bootstrap"

version := "0.1"

scalaVersion := "2.9.1"

libraryDependencies <++= scalaVersion { sv =>
("org.scala-lang" % "scala-swing" % sv) ::
("org.scala-lang" % "jline" % sv % "compile") ::
("org.scala-lang" % "scala-compiler" % sv % "compile") ::
("org.scala-lang" % "scala-dbc" % sv % "compile") ::
("org.scala-lang" % "scalap" % sv % "compile") ::
("org.scala-lang" % "scala-swing" % sv % "compile") ::Nil
}

mainClass in assembly := Some("fr.janalyse.script.Bootstrap")

jarName in assembly := "bootstrap.jar"

you'll be able to directly run any scala script like that :

#!/bin/sh
exec java -jar bootstrap.jar "$0" "$@"
!#

cd("/etc/")

"ls" #| "grep net" !

Thanks to the assembly SBT plugin, you've generated a standalone executable jar, which contains the scala compiler, and our custom scala script startup mechanism.
In a next POST, I'll describe more in detail a new bootstrap implementation that will bring #include feature to scala script.