Java.next: Common ground in Groovy, Scala, and Clojure, Part 3

Rethinking exceptions, expressions, and emptiness

The last of three installments about commonalities among Clojure, Scala, and Groovy investigates how these languages handle exceptions, expressions, and null— all problem areas for the Java™ language. Each of the Java.next languages addresses the shortcomings of the Java language through a unique implementation that highlights that language's characteristics.

Neal Ford, Director / Software Architect / Meme Wrangler, ThoughtWorks Inc.

Neal FordNeal Ford is Director, Software Architect, and Meme Wrangler at ThoughtWorks, a global IT consultancy. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, and video/DVD presentations, and he is the author or editor of books spanning a variety of technologies, including the most recent Presentation Patterns. He focuses on designing and building large-scale enterprise applications. He is also an internationally acclaimed speaker at developer conferences worldwide. Check out his website.



14 May 2013

Also available in Chinese Russian Japanese

About this series

The Java legacy will be the platform, not the language. More than 200 languages run on the JVM, each bringing interesting new capabilities beyond those of the Java language. This series explores three next-generation JVM languages — Groovy, Scala, and Clojure — comparing and contrasting new capabilities and paradigms. The series aims to give Java developers a glimpse into their own near future — and help them make educated choices about the time they devote to new-language learning.

In the last installment, I covered innovative ways in which the Java.next languages reduce the ceremony and complexity that bedevil the Java language. In this installment, I show how these languages improve several Java sore spots: exceptions, statements versus expressions, and edge cases around null.

Expressions

One legacy that the Java language inherited from C is the distinction between programming statements and programming expressions. Examples of Java statements are code lines that use if or while, and ones that use void to declare methods that have no return value. Expressions, such as 1 + 2, evaluate to a value.

This split started in the earliest programming languages, such as Fortran, where the distinction was based on hardware considerations and a nascent understanding of programming-language design. It remained in many languages as an indicator of action (statement) versus evaluation (expression). But language designers independently and gradually realized that languages can consist entirely of expressions, ignoring the result when the outcome isn't of interest. Virtually all functional languages eliminate the distinction entirely, using only expressions.

Groovy's if and ?:

In the Java.next languages, the split between traditional imperative (Groovy) and functional (Clojure and Scala) languages illustrates the evolution toward expressions. Groovy still has statements, which are based on Java syntax, but adds more expressions. Scala and Clojure use expressions throughout.

Inclusion of both statements and expressions adds syntactic clumsiness to languages. Consider the if statement in Groovy, inherited from Java. It has two versions, which are compared in Listing 1: the if statement for decisions, and the cryptic ternary operator ?::

Listing 1. Groovy's two ifs
def x = 5
def y = 0
if (x % 2 == 0)
  y = x * 2
else
  y = x - 1
println y   // 4

y = x % 2 == 0 ? (x *= 2) : (x -= 1)
println y   // 4

In the if statement in Listing 1, I must set the value of x as a side effect because the if statement has no return value. To perform the decision and assignment at the same time, you must use the ternary assignment, as in the second assignment in Listing 1.

Scala's expression-based if

Scala eliminates the need for the ternary operator by allowing the if expression to handle both cases. You can use it just like the if statement in Java code (ignoring the return value) or use it in assignments, as in Listing 2:

Listing 2. Scala's expression-based if
val x = 5
val y = if (x % 2 == 0) 
          x * 2
	else
	  x - 1
println(y)

Scala, like the other two Java.next languages, doesn't require an explicit return statement for methods. In its absence, the last line of the method is the return value, emphasizing the expression-based nature of methods in these languages.

As when you act and set values in Java and Groovy code, you can encapsulate each response as a code block, as in Listing 3, and include any desired side effects:

Listing 3. Scala if + side effects
val z = if (x % 2 == 0) {
              println("divisible by 2")
	      x * 2
	    } else {
              println("not divisible by 2; odd")
	      x - 1
	    }
println(z)

In Listing 3, I print a status message for each case in addition to returning the newly calculated value. The order of the code lines within the block is important: The last line of the block represents the return value for that condition. Thus, you must exercise special care when you mix evaluation and side effects with expression-based if.

Clojure's expressions and side effects

Clojure also consists entirely of expressions but goes even further in differentiating side-effect code from evaluation. The Clojure version of the previous two examples is expressed in a let block, in Listing 4, which allows the definition of locally scoped variables:

Listing 4. Clojure's expression-based if
(let [x 5
      y (if (= 0 (rem x 2)) (* x 2) (- x 1))]
  (println y))

In Listing 4, I assign x a value of 5, then create the expression with if to calculate the two conditions: (rem x 2) calls the remainder function, similar to the Java % operator, and compares the result to zero, checking for a zero remainder when you divide by 2. In Clojure's if expression, the first argument is the condition, the second the true branch, and the third is the optional else branch. The result of the if expression is assigned to y, which is then printed.

Clojure also allows blocks (which can include side effects) for each condition but requires a wrapper such as (do ...). The wrapper evaluates each expression in the block, by using the last line as the block's return value. Evaluating a condition and a side effect is illustrated in Listing 5:

Listing 5. Explicit side effects in Clojure
(let [x 5
      a (if (= 0 (rem x 2))
          (do
            (println "divisible by 2")
            (* x 2))
          (do
            (println "not divisible by 2; odd")
            (- x 1)))]
  (println a))

In Listing 5, I assign a the return value of the if expression. For each of the conditions, I create a (do ...) wrapper, allowing an arbitrary number of statements. The last line of the block is the return for the (do...) block, similar to the Scala example in Listing 3. Ensure that the target return value is evaluated last. The use of (do...) blocks in this way is so common that many constructs in Clojure (such as (let [])) already include implicit (do ...) blocks, eliminating their need in many cases.

The contrast in treatment of expressions between Java/Groovy code and Scala/Clojure code is indicative of the general trend in programming languages to eliminate the unnecessary statement/expression dichotomy.


Exceptions

For me, the most "seemed like a good idea at the time" feature of Java programming was checked exceptions and the ability to broadcast (and enforce) exception awareness. In practice, it became a nightmare, forcing needless handling (and mishandling) of exceptions out of context.

All the Java.next languages use the exception mechanism that is already built into the JVM, with syntax based on Java syntax and modified for their unique syntax. And they all eliminate checked exceptions, converting them into RuntimeExceptions when they're encountered during Java interop.

Scala exhibits a couple of interesting behaviors in the translation of the Java exception-handling mechanism to work in its expression-based world. First, consider the fact that exceptions can be the return value of expressions, as illustrated in Listing 6:

Listing 6. Exceptions as return values
val quarter = 
  if (n % 4 == 0)
    n / 4
  else
    throw new RuntimeException("n must be quarterable")

In Listing 6, I assign either one-fourth the value of n or an exception. If the exception triggers, the return value means nothing because the exception propagates before the return is evaluated. The legality of this assignment might seem odd, considering that Scala is a typed language. The Scala exception type is not a numeric type, and developers are unaccustomed to considering the return value of a throw expression. Scala solves this problem in an ingenious way, by using the special Nothing type as the return type of the throw. Any is at the top of the inheritance hierarchy in Scala (like Object in Java), meaning that all classes extend it. Conversely, Nothing lives at the bottom, making it an automatic subclass of all other classes. Thus, it is legal for the code in Listing 6 to compile: either it returns a number, or the exception triggers before a return value is set. The compiler reports no error because Nothing is a subclass of Int.

Second, the finally block has interesting behaviors in an expression-based world. Scala's finally block works like the others functionally but exhibits a nuanced behavior around return values. Consider the code in Listing 7:

Listing 7. Scala's return from finally
def testReturn(): Int = 
  try {                               
    return 1                          
  } finally {                         
    return -1                         
  }

In Listing 7, the overall return value is -1. The finally block's return "overrides" the one from the body of the try statement. This surprising result occurs only when the finally block includes an explicit return statement; the implicit return is ignored, as illustrated in Listing 8:

Listing 8. Scala's implicit return
def testImplicitReturn(): Int = 
  try {
    1 
  } finally {
   -1
  }

In Listing 8, the return value of the function is 1, illustrating the intended use of the finally block as a place for cleaning up side effects rather than for resolving expressions.

Clojure is also wholly expression-based. The return value of a (try ...) is always either:

  • The last line of the try block in the absence of an exception
  • The last line of the catch block that caught the exception

Listing 9 shows the syntax for exceptions in Clojure:

Listing 9. Clojure's (try...catch...finally) block
(try  
  (do-work)
  (do-more-work)
  (catch SomeException e  
    (println "Caught" (.getMessage e)) "exception message")
  (finally  
    (do-clean-up)))

In Listing 9, the return value of the happy path is the return from (do-more-work).

The Java.next languages take the best parts of the Java exception mechanism and discard the cumbersome parts. And, despite some implementation differences, they manage to incorporate exceptions into an expression-based perspective.


Emptiness

In a legendary conference presentation at QCon London in 2009, Tony Hoare called his invention of the "null" concept for ALGOL W — an experimental object-oriented language that he introduced in 1965 — "a billion dollar mistake" because of all the problems it caused in the programming languages that it influenced. The Java language itself suffers from edge cases around null, which the Java.next languages address.

For example, a common idiom in Java programming guards against a NullPointerException before you attempt method invocation:

if (obj != null) {
    obj.someMethod();
}

Groovy has encapsulated this pattern into the safe navigation operator, ?.. It automatically does a null check of the left side and attempts only the method invocation if it is non-null; otherwise it returns null.

obj?.someMethod();
def streetName = user?.address?.street

Invocations of the safe navigation operator can also be nested.

Groovy's closely related Elvis operator, ?:, shortens the Java ternary operator in the case of default values. For example, these lines of code are equivalent:

def name = user.name ? user.name : "Unknown" //traditional ternary operator usage

def name = user.name ?: "Unknown"  // more-compact Elvis operator

When the left side might already have a value (generally a default), the Elvis operator preserves it, otherwise setting a new value. The Elvis operator is a shorter, expression-leaning version of the ternary operator.

Scala enhanced the concept of null and made it a class (scala.Null), along with a related class, scala.Nothing. Null and Nothing are at the bottom of the Scala class hierarchy. Null is a subclass of every reference class, and Nothing is a subclass of every other type.

Scala offers an alternative to both null and exceptions to indicate absence of value. Many Scala operations on collections (such as get on a Map) return an Option instance, which contains either of two parts (but never both): Some or None. The REPL interaction in Listing 10 shows an example:

Listing 10. Scala's return of Option
scala> val designers=Map("Java" -> "Gosling", "c" -> "K&R", "Pascal" -> "Wirth")
designers: scala.collection.immutable.Map[java.lang.String,java.lang.String] = 
	   Map(Java -> Gosling, c -> K&R, Pascal -> Wirth)

scala> designers get "c"
res0: Option[java.lang.String] = Some(K&R)

scala> designers get "Scala"
res1: Option[java.lang.String] = None

Notice in Listing 10 that the return from the successful get is an Option[java.lang.String] = Some(value), whereas a failed lookup results in a None. A technique for unwrapping values from collections uses pattern matching, which is itself an expression that allows access and unpacking in a single terse expression:

println(designers get "Pascal" match { case Some(s) => s; case None => "?"})

Option allows a better expression of emptiness than null alone, especially considering the syntactic support for its use.


Conclusion

In this installment, I delved into three problem areas in the Java language: expressions, exceptions, and null. Each of the Java.next languages addresses the pain points from Java, but each in its idiomatic way. The presence of expressions changes the idioms and options for seemingly unrelated concepts such as exceptions — further illustration that language features are highly coupled to one another.

Java developers sometimes fall into the habit of thinking that inheritance is the only way to extend behavior. In the next installment, I show how the Java.next languages offer many more-powerful alternatives.

Resources

Learn

  • Java.next: Common ground in Groovy, Scala, and Clojure, Part 1 (Neal Ford, developerWorks, March 2013): Address the inability to overload operators in the Java language with the Java.next languages: Groovy, Scala, and Clojure).
  • Java.next: Common ground in Groovy, Scala, and Clojure, Part 2 (Neal Ford, developerWorks, April 2013): Reduce boilerplate and complexity with the syntactic conveniences offered by Java.next languages.
  • Java.next: The Java.next languages (Neal Ford, developerWorks, January 2013): Explore the similarities and differences of three next-generation JVM languages (Groovy, Scala, and Clojure) in this overview of the Java.next languages and their benefits.
  • Scala: Scala is a modern, functional language on the JVM.
  • Clojure: Clojure is a modern, functional Lisp that runs on the JVM.
  • Groovy: Groovy is a dynamic language for the JVM.
  • Explore alternative languages for the Java platform (May 2012): Follow this knowledge path to investigate developerWorks content about various alternative JVM languages.
  • Language designer's notebook (April 2010-Octover 2011): In this developerWorks series, Java Language Architect Brian Goetz explores some of the language design issues that presented challenges for the evolution of the Java language in Java SE 7, Java SE 8, and beyond.
  • Functional thinking: Explore functional programming in Neal Ford's column series on developerWorks.
  • More articles by this author (Neal Ford, developerWorks, June 2005-current): Learn about Groovy, Scala, Clojure, functional programming, architecture, design, Ruby, Eclipse, and other Java-related technologies.
  • developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.

Get products and technologies

  • Download IBM product evaluation versions and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=928692
ArticleTitle=Java.next: Common ground in Groovy, Scala, and Clojure, Part 3
publish-date=05142013