Wednesday, December 19, 2012

Getting Scala with Scalatron

I've always liked Scala, but like many of my early dating experiences, it has been from afar... wistfully looking at Scala projects on github, thinking that one day I'd take the plunge and ask one of those nice projects out.

Then I found a geek that was easy for me to connect with: Scalatron.  Here ends the dating analogy.

Scalatron is a framework for writing competitive AI bots that compete in an online arena.  But Scalatron is also super easy (hey, the dating analogy is over, remember?).  Download the zipfile, extract it, type java -jar Scalatron.jar and jump into the very nice tutorials, and get quick visual feedback on the code you're writing.  Plus, it's fun and you can kill stuff.

Scala's Goals

Scala's goals remind me a lot of Java's goals when it first came out.  Java was a Real OO language, where everything is possible (mostly), but the easiest way to do things was the OO way.  At least as compared to C++ or interpreted languages at the time.  I think it's maybe being eclipsed by C#, but that's a different article.

Scala's goals are to be a Real Functional language where everything is possible, but the easiest way to do things is the Functional way.  So it has all the tools to make it really obvious when you're being non-functional.  val vs var, for example really prompts you to say, "Hey man, why you gotta go and make this mutable?  You trying to make problems for yourself?"

And it wants to have a type system that doesn't get in the way, or leave you in the dark.  This addresses my big complaint with dynamic languages.  Dynamic languages are nice right up until you're working with unfamiliar codebases, which unfortunately is immediately.  The Ruby framework routinely made me upset by having no types defined at all, not even named.  What does this method take?  A hash.  Ok, not helpful.  So I open up the code and ask, "what does it do with that hash?  Oh, it passes it to another method.  great."  And pretty soon you figure out that it's hashes all the way down.

"But Kevin!" the dynamic programmers say, "That's the flexibility!  You can pass it anything, that's the point!"  Anything?  Yeah, what if I pass it some drek?  Will it like it?  I suspect it won't be good for either of us.

Type Inference to Tuples

The middle ground.  Strong types and a compiler that cares (vs leaving us to write unit tests to do what a typing system could do for us) but no more of Collection collection = collection collectioncollectioncollectioncollectioncollection.  It's interesting to work with a type inference system, because it's such a perfect window into why we had no inference before.  I find myself looking around the code saying, "Wait, what type is this again?"  The compiler knows, but I don't.  It's like one of those logic puzzles that is 10 steps deep in transitive logic.  And if I were writing code in vi or something, it would be a pain.  But I don't do that anymore.  I'm not using an IDE right now, but I'm sure that will be my main way of working, and I have full confidence that the Scala IDEs will let me hover over something and see its type.

But Functional programming's no side effects rules means that you're frequently going to have multiple return values, which in many languages generates a lot of types.  But this is Functional programming, so Tuples are built right in with both type inference and static constructors to keep the code from being redundant.  Syntactic sugar?  That's a funny way to spell 'readability'.
val myTuple = ('something','somethingElse')
Except I do have a complaint:  _1 and _2 aren't particularly good names for the contents of a Tuple.  Time will tell if this is a problem, but I suspect it might be.  The typing system will tell me I get three strings, but what are they?  There's some more syntax so that you can assign things directly to meaningful names, which is nice:
val (firstName, lastName) = someTupleReturningFunc()
But how did I know what that function did in the first place?  Especially if it's cleverly passing tuples from its component parts?  Ideally I'd like for the API to be discoverable, and type information isn't always enough.

I guess at that point you create an object (via Scala's object keyword, which is distinct from class) and give everything proper names.  I read online about using a case class to extend a tuple, which looks nice but apparently has some real complications.  I'm just a novice, of course, perhaps there's a better solution still that doesn't fall all the way back to making pojo beans.

The first touch that I have with real Functional programming is almost always with some kind of mapping function.  And that's even the famous Function Heard Round The World, Google's MapReduce.  I like how it looks in Scala, returning a Tuple for a given input.

No return statements?  What about readability and accidents?

And here again is an "aha" moment for me.  The Scala 'way' is to have no return statements.  This is an element of consistency when looking at quick function definitions.  And Scala, like Javascript, loooves functions.  And if most functions are going to be one-liners, having a return keyword in there is just clutter.  So the quick-closure definition of a function doesn't use the return keyword.  And neither should regular functions.

Language Habits: names for everything

I can see that my Java habits are going to give my initial Scala code a strong accent.  Like the charming Eastern European habit of dropping articles like "a" and "the", I'm going to probably name lots of stuff that I don't need to in the beginning.  Right from Scalatron's example:
val rest = tokens(1).dropRight(1)               
val params = rest.split(',')                    
val strPairs = => s.split('='))    
val kvPairs = => (a(0),a(1)))   
val paramMap = kvPairs.toMap       
That's familiar code right there.  But we don't need all those names, technically.  The way all the cool kids are doing it these days is this:
val paramMap =
    .map(a => (a(0),a(1)))

Get off my lawn!  I mean, uh, what a charming way to string everything together.  But what it represents here is the Functional point of view.  These are all simultaneous operations we are invoking on the original value.  The intermediate names aren't useful.  I have to admit I'm not convinced.  But then I can't explain to my Lithuanian friends why "a" and "the" are important, either.

Function scoped Functions

And hey, if we're going to have functions that we pass around like variables, there's no reason we can't have locally-scoped functions is there?  In fact, this is pretty key.  In OO, methods exist to change the state of the object.  So we had only a couple of levels of visibility of methods.  If you felt like you wanted to hide some methods from some other methods, this was a pretty good hint that you actually need to divide the object into parts.

But now we don't usually have state to deal with, and we're going to be using a LOT more functions.  So there you go, dawg.   

Objects vs Classes vs Case Classes.

This distinction seems a little fine on first blush.  First, terminology.  Just like in Java and other OO languages, objects are things that are instantiated, and classes are definitions and names for things that you may choose to instantiate (or not, I suppose).  And Case Classes are a special thing that ... some people apparently hate.  I think that's where I'm going to have to pick up next time.


tuck182 said...

Think of "object" in scala like a singleton. In fact, since scala has no "static" keyword, if you need a static you'd put it in an object:

object Foo {
val logger = LoggerFactory.getLogger(classOf[Foo])

class Foo {
import Foo._

logger.debug("Created a Foo")

Case classes are basically classes with some syntatic sugar (like automatic apply and unapply methods), a single constructor (via apply) and immutable properties. They're particularly useful in match statements (partial functions).

Case objects are singleton case classes with no properties.

tuck182 said...

I mention all this mainly because "return a case object as a tuple" doesn't really make sense, but using a case class does.