Thursday, July 16, 2009

Safe navigaion in Scala, take 2

OK, revisiting this topic after a very short time, I have a new implementation, again using that interesting anonymous class technique from James Iry's comment:
implicit def richOption[T](x : T) = new {
def ??[B](f : T => B):B = if (x != null) f(x) else null.asInstanceOf[B]
}
I changed the operator from "?!" to "??" because that's a lot easier to type and I chose "??" instead of "?" so people wouldn't confuse it with the questi-colon operator from Java and C. It's stand-alone (doesn't depend on Option or anything) and I had to use a cast to get the types to work out properly, but the test case from my last post does work
println(p3??(_.bud)??(_.bud)??(_.bud)??(_.name.drop(1)))
prints null and
println(p3??(_.bud)??(_.bud)??(_.name.drop(1)))
prints ubba.

For those new to Scala, the expressions in parentheses are anonymous functions because they use "_" as identifiers. (_.bud) expands to (x => x.bud), where Scala chooses an x that is unique in the scope of the expression. Of course, you can put whatever you want in the final block there, or the other blocks. You could say this instead
p3??(_.bud)??(_.bud)??(x => println(x.name.drop(1)))
You might want to do this if you only want to print provided that the full path exists for instance, which demonstrates that this construct is more powerful than Groovy's, because each step can be more than just another increment along the path.

This has an advantage over the previous solutions of not requiring the ! operator at the end to extract the value out of the option because the ?? operator returns a raw value instead of an Option. Not having used Scala for more than a week (I haven't even used LISP since the late 80s), I can't speak as an authority on when it's appropriate to use Options, but this seems to me like an alternative to Option for at least some cases.

7 comments:

  1. I don't mean to be rude, but that's damn near the ugliest thing I've ever seen in code.

    ReplyDelete
  2. Hey, that's OK, I don't mind if you think it's ugly; It's more concise than a bunch of if statements. I'm not sure if I'll ever use it, but there it is anyway :)

    ReplyDelete
  3. I'd have to agree with Charlie. Part of the great thing about using JRuby is its elegant, concise, intelligible language along with the power of the JVM. This is just unintelligible.

    ReplyDelete
  4. Why does it matter that's it' consise ? Even you won't be able to read this in 6 month.
    This is even worst than some ugly Perl I've seen.

    ReplyDelete
  5. Hi Bill,

    I think you've been pulled in by the siren call of operators. They are seductive, but I think it's best to try and resist them except when their meaning is obvious. The rule I use is that I try really hard to not use an operator unless it is very obvious to people unfamiliar with my API what the operator means. I'd say ?? would leave people wondering WTF??, because it isn't clear what it means. They'd need to look up your API, and even then I think the resulting code can look a bit cryptic.

    I do think Groovy's Elvis operator (?:) and Safe Navigation operator (?.) look pretty nice, and make for readable code. They are part of the language, so it it something you can expect users will learn the meaning of. I'd suggest you consider naming ?? something like safeNav, that indicates a bit about what it means. Then the client code would look like:

    scala> implicit def addSafeNavMethod[T](x: T) = new {
    | def safeNav[B](fun: T => B): B = | if (x != null) fun(x) | else null.asInstanceOf[B]
    | }
    addSafeNavMethod: [T](T)java.lang.Object{def safeNav[B]((T) => B): B}

    scala> val ex1 = new Exception
    ex1: java.lang.Exception = java.lang.Exception

    scala> val ex2 = new Exception("Hello, world!")
    ex2: java.lang.Exception = java.lang.Exception: Hello, world!

    scala> ex1.getMessage safeNav (_.substring(6))
    res15: java.lang.String = null

    scala> ex2.getMessage safeNav (_.substring(6))
    res16: java.lang.String = world!

    Bill

    ReplyDelete
  6. I much prefer using Option together with for-comprehensions. For example, if your bud method returns Option:

    for {
    a <- p3
    b <- a.bud
    c <- b.bud
    } yield c.name.drop(1)

    Or, if the functions actually *applied* to their values, rather than being member methods:

    p3 flatMap bud flatMap bud map { _.name.drop(1) }

    Both of these are significantly more composable, and far more readable IMHO than the ?? operator you proposed. It's an interesting example of operator overloading in Scala, but I don't think it's one I'll be using any time soon

    ReplyDelete
  7. If you want really cool and abstruse "concise" code, I recommend that you buy a special keyboard and start coding in APL! I broke my poor brain on that a few years ago and your code, above, is just plain scaring me away from Scala!

    APL is a WORN language (Write Once Read Never) and it looks like you're trying to turn Scala into another one!

    I vote a solid "no" on such language "constructs!"

    ReplyDelete