Monday, October 25, 2010

Scala Option

EVERY SINGLE PERSON on the internet has written about this. And me!

The thing is, I didn't fully understand most of the stuff that was written about monads. Still don't - but I wonder if that's not because it's sufficiently weird that people just try to cram their own metaphor onto it and then we're dependent on having consistent mental models.

Which we don't. Read any code from anywhere and tell me I'm wrong.

So anyway, Options: Basically, they're Monads.

So anyway, Monads:
[shrug]

What I'M using them for is doing null safe stuff in Scala. For instance:
private def loadDetails(id:Int)={
  val uri = "%s/%s/details.xml".format(server,id)
  try{
    Some(WS.url(uri).authenticate(username,password).get.getString)
  }
  catch{
    case e: Exception => None
  }
}

I think this is fairly idiomatic. Try something, and instead of launching exceptions out to cause runtime crashes some indeterminate time later, wrap it now nice and tight in something which is well typed to be "This might have a problem. You should check if it has a problem before you try to use it". How is this different to just declaring throws and handles? Well, it's on the value you're passing instead of the receiving location. That's good, gives you both flexibility and strength.

But where I was stuck was in working out what to DO with this list of Maybe(Details). Oops, sorry that's pseudo Haskell. It's a Some[Details]. You typically would deconstruct them like this:

foreach(val uri : uris)
uri match{
case Some(value) => operateOn(value)
case None => someSpecificHandlingOfNotHavingAUri
}

The thing is, most of the time when it comes to specifically handling not having a uri I just want to ignore it and move on. Which led to me writing some seriously bullshit code like this:

uris.filter(_ != None).map(some => some match{case Some(value) => value})

Which is TERRIBLE and difficult to read. All it's trying to do is remove all the nonvalues from the lists. The Somes that are really Nones. And the most annoying thing is that I'm doing Options in the first place because I'm halning Scala, and I'm vigorously opposed to using nulls. But if I HAD been using nulls, it would look like this:

uris.filter(_ != null)

And we'd be done. Want to see those again? No, me neither. It's heartbreaking. BUT SOFT! What light through yonder window breaks! There's hope, friends. And as usual it's because I am a HUGE IDIOT.

See, here's another thing about Monads (it will be the first thing about monads but who's counting? And should they be capitalized or what? They're magic to me at the moment because they're technology sufficiently advanced. So caps until I understand them.)

The thing is, firstly, they're a container. They contain a thing. They are specialized to contain only that sort of thing. So, for instance, an Option[Int] might be Some(3) or None. So you have a way to wrap an Option around an Int. It's called applying. So

option:Option[Int] = Option.apply(3)

and then it's an Option[Int]. And you can unapply it later. In scala you tend to do it through deconstructive matching.

Anyways, the other thing I was completely missing about Options is that they are containers. Wait, that was the first thing. Let me try again. They're lists. Or enumerables. Or whatever you call the thing you can go through from the beginning to the end in your paradigm of choice. Seq? Cool, anyway one of those.

That means that this is a perfectly valid idiom:

foreach(val value : Some(3))
println(value)

And the result is that it prints out 3. Let's have that again? Iterating over an Option released the value. Or not, depending if it has one.

So remember that bloody terrible guy from before? This guy:

uris.filter(_ != None).map(some => some match{case Some(value) => value})

If I weren't a complete idiot it would have read like this:

uris.flatMap(value=>value)

Because flatMap applies a (surprise) flatten and a map. So, for instance, this list of options:

List(Some(3), None, Some(4), None)

mapped to value=>value results in this:

List(List(3), List(), List(4), List())

And flattening that results in List(3,4).

Thus uris.flatMap(value=>value) really isn't that much uglier than uris.filter(_!=null). And is much more scalaish.

(Months later, when I happened across my own blog searching for Scala stuff):

Actually you'd just flatten it. flatMap(v=>v) is exactly the same as flatten, given that the map hof is just identity.