So, another summer holiday is at an end, and another programming interval. This time I was using my LSystem Explorations as a way to get more practice with Scala and Android.
Random aside: I find it hard to get excited about non-JVM languages nowadays. I develop on a Mac and deploy to Linux, Windows (sometimes) and now Android. I really don't want to worry about whether the language or platform I'm using has native components hidden somewhere inside. I've spent enough time doing "./configure" in the past to be seriously bored with non-portability. That was even going between two Unixes (Solaris and Linux)! I know Java is really "write once, debug everywhere" (e.g. Graphics2D problems on the Mac), but at least these are problems in particular library features, not a total inability to run.
Anyway, what have I learned? Scala in Eclipse has become usable enough for me with the latest Eclipse plugin for me to use Scala as my main Programming Language. It's still the case that the Scala builder gets confused too often and you have to do a Project->Clean. This even happens with pure Scala builds, and not just with mixed Java/Scala projects. The Editor integration is also flaky e.g. copy and paste between files can cause errors, and you can forget about consistently completing a Rename Refactor. However, even given all that, the ability to mix Scala and Java in one project is what really does it for me. Overall, it's still basic compared to the JDT. I don't yet want to push it for my day job, but for personal projects, where you can absorb the extra time fixing things if you break them, it's fine.
Scala as a language is really growing on me. As previously mentioned, I was working through the "Programming in Scala" book. Even though Scala fundamentally has a small core, it has the appearance of a much larger language. I really needed to read most of this book to get past quite a few roadblocks, but I did manage it.
For example, I'd looked at parser combinators before but found the examples posted at the time and the scaladoc impenetrable. This is not too surprising as operators in Scala appear as methods on the class, so class-level docs can't really tell you much about how to use them in context. The JSON example in the book was large enough to give me sufficient transferrable knowledge to write a simple parser for LSystem descriptions:
import scala.util.parsing.combinator._
object LSystemLanguage extends RegexParsers {
// This parses "B; F -> FF, B -> F[+B]F[-B]+B; FB" into an LSystem
def parse(spec: String) : LSystem = {
parseAll(lSystem, spec) match {
case Success(l, _) => l
case Failure(m, _) => throw new IllegalArgumentException(m)
case Error(m, _) => throw new IllegalArgumentException(m)
}
}
def lSystem = axioms~";"~rules~";"~visible ^^
{ case axioms~";"~rules~";"~visible => LSystem(axioms, rules, Set() ++ visible) }
def rules = repsep ( rule, "," )
def rule = variable~"->"~rep( variable | constant ) ^^
{ case lhs~"->"~rhs => new Rule(lhs, rhs) }
def axioms = rep(element)
def visible = rep(element)
def element = variable | constant
def variable =
"[A-Z]".r ^^ ( (s : String) => { Var(s.charAt(0)) } )
def constant =
("[a-z]".r | literal("+") | literal("-") | literal("[") | literal("]") ) ^^
( (s : String) => Constant(s.charAt(0)) )
}
Apologies for the probably non-idiomatic Scala (and lack of syntax-highlighting on this site, I'll upgrade one day, honest), but it has the important property that it works, as opposed to most of my previous attempts. From further reading elsewhere, I get the impression that they are papering over some details (the !RegexParser combines both tokenisation and parsing, which they don't cover separately from what I read of the book). However, who cares, it works!
I'm now seriously considering supporting the full Fractint LSystem syntax, which I wouldn't have considered doing in Java. Obviously, you can do all this with JLex etc, but that means going back and forward between languages and probably, gack, writing an ant script. Seriously, I spent more time during my holiday getting a simple ant task to work than I did writing this parser.
Modulo one instance where Eclipse crashed and I had to rebuild my workspace, programming in Scala using Eclipse has been fun. The combination of an object-functional language, a half-decent IDE and rich platform libraries is very powerful. It's important to emphasize what this can give you. For me it means that if I have a spare hour or so in which to add a new feature or tweak an existing one (like extending the parser) I'll consider doing it and expect to complete it, and enjoy it.
Technically, I'm capable of getting the same feature implemented in Java, in combination with other tools. However, I'd start out with a big "harumph" as I picture the fields of verbosity laid out before me. It would definitely not be fun.
This brings me to Android. The Scala compiler spits out JVM bytecodes. The Android platform, though not strictly a Java VM, does claim to support Java class files. Theoretically, I should be able to write a library in Scala and use it on Android.
I am explictly not trying to write an Android app in Scala. Instead, I created an LSystem library, written in Scala, which exposes itself solely through these interfaces:
public interface LSystemViewFactory {
public LSystemView evaluate(String spec, double angle, int rounds);
}
public interface LSystemView {
public void renderTo(Renderer renderer);
}
public interface Renderer {
interface Point {
double x();
double y();
}
public void addPath(Iterable<Point> path, int length);
}
In this way, I was trying to avoid relying on any special help that could come from using one Scala project with another. It does work, in a way.
Warning: what follows is a history of my stubborn depth-first method of getting something showing on my phone. There may a better method.
In Eclipse 3.4, you need only make an Android project depend on a Scala project and everything will compile. However, when you run it on the phone, it will be missing the required Scala class definitions. You can download the scala-library.jar and add it as a required library. The Dex compiler then starts churning away on this file, heating up your lap as a side-effect, and then fails with a compiler error. Schäde.
There is some joojoo in "scala-library.jar" that the Dex compiler just doesn't like. I think it is an unsupported annotation of some kind, probably somewhere in scala.xml.transform, but since it takes a minute or so for dex to do its thang to this library, I decided to adopt a slash and burn approach, by quickly removing anything I knew I didn't need on the phone. So, goodbye to any of scala.actors, scala.reflect, scala.testing or scala.xml.transform.
Random Aside: it was at this point I had the afore-mentioned elbow injury^H^H^H^H...problem with ant. It is frustratingly hard to get ant to behave in any sort of sensible and consistent manner. In this case, I had the temerity to require more than one exclude pattern (for the multiple packages). The docs claim that you can put them in one string, comma-separated or space-separated; obviously multiple elements are for wimps and no-one will ever need a pattern that contains both a literal space and a comma. I tried this, and it didn't seem to work. There is, of course, no "verbose" option on the "jar" task so I couldn't confirm whether my patterns were getting picked up. I ended up using a separate "excludesfile". A muted "hurrah" and I'm back to work. It's at times like this that I briefly consider moving back to Make. But only briefly.
This did the trick and dex now compiled the scala-library.jar. My Android Activities and Views (which depend, indirectly, on Scala classes) successfully resolved all references and I could run my app.
All is not satisfactory, however. As previously-mentioned, the dex compilation phase now takes a minute. This compilation happens in the background whenever you make a change to any file, whatsoever. That's right: tappitty-tap, muscle-memory says "auto-save", and bzzt you've got a minute penalty. On top of this, the dex loader on the phone takes about 10 to 15 seconds to parse the app description. I haven't yet packaged the app and used it outside of the debugger, so I can't tell if this 15 second penalty is incurred ever time you launch it.
Both the IDE and run-time problems should be solved by running proguard over it. If I choose the set of interfaces above as the ones to preserve, then I should be left with a much smaller working set of classes. I didn't try this, partly because I ran out of time, but mostly because it would probably involve writing an ant build file. Can you tell I don't like ant?
Phew, so after all that I have a (slightly buggy) drawing of an LSystem on my phone:
For reference, here's the same LSystem rendered using the same library, but with a Java2D backend: