Software has two ingredients: opinions and logic (=programming). The second ingredient is rare and is typically replaced by the first.
I blog about code correctness, maintainability, testability, and functional programming.
This blog does not represent views or opinions of my employer.

Sunday, May 27, 2012

Imperative curlies 7: switch statements


My programming is changing.  One of the visible aspects of this change is fewer if statements and more switch statements.  During work hours I program in Groovy and GRAILS, but I think the changes in my coding style are more motivated by reading about functional/declarative programming and SCALA than any reading on Groovy.  Still, I am happy with Groovy,  the language fits my coding evolution very well.
(Side Note:  if Groovy was more functional I might have still liked if statements, ifs are a different beast in functional languages.)

The functional programming concept of pattern matching is something I have learned from SCALA.  Groovy has a great support for switch statements so pattern matching can be implemented in Groovy quite well.
 Here is how I might have coded a year ago  (Test is a class representing a test or an exam, it knows a test score):
01: float score = test.getScore();//some object representing an exam or
                                    a test with a score
02: String grade = null;
03: 
04: if(score >= 90) {
05:   grade = "A";
06: } else if(score >= 80) {
07:   grade = "B";
08: } else if(score >= 70) {
09:   grade = "C";
10: } else if (score >=60) {
11:   grade = "D";
12: } else {
13:   grade = "F";
14: }
This might have been my Java code, C# code, or my first Groovy code.  Today I would prefer this code: (The rest of the post is in Groovy.)

01: Closure scoreForA = { Test t->
02:  t.score >= 90;
03: }
04: Closure scoreForB = { Test t->
05:  t.score >=80 && t.score < 90
06: }
07: Closure scoreForC = { Test t->
08:  t.score >=70 && t.score < 80
09: }
10: Closure scoreForD = { Test t->
11:  t.score >=60 && t.score < 70
12: }
13: Closure scoreForF = { Test t->
14:  t.score < 60
15: }

16: Test test = . . .
17: String grade

18: switch(test) {
19:  case scoreForA: grade = 'A'; break
20:  case scoreForB: grade = 'B'; break
21:  case scoreForC: grade = 'C'; break
22:  case scoreForD: grade = 'D'; break
23:  case scoreForF: grade = 'F'; break
24: }
As per my previous posts, one liner functions do not contribute to the curly count. 
So yes, I have a significant reduction in the number of curly braces. So what are the benefits?
One obvious difference is that I have separated the declaration of test score conditions for different grades from the conditional logic which calculates the grade.   This allows me to manage these independently.  Think of scoreForX closures as instance fields on my grade assignment class, think of them as something that can be set/dependency injected/configured without any changes to the test grade assignment logic itself.

So, to have some fun with this lets define the following:
01: //class defining rages 
02:class Curve {
03: ObjectRange rangeForA 
04: ObjectRange rangeForB
05: ObjectRange rangeForC
06: ObjectRange rangeForD
07: ObjectRange rangeForF
08:}
and
01: //generic function defining score to grade conversion
02:Closure gradeAssignment = {Curve curve, String testgrade, Test t ->
03:   curve."rangeFor$testgrade".containsWithinBounds(t.score)
04:}
This code should illustrate the power of the declarative programming (note my conditional grade calculation logic has not changed):
01: def curve = new Curve(rangeForA: (89.0..100.0),
                          rangeForB: (79.0..<89.0), 
                          rangeForC: (70.0..<79.0), 
                          rangeForD: (60.0..<70.0), 
                          rangeForF: (0.0..<60.0))


02: Closure scoreForA = gradeAssignment.curry(curve, "A")
03: Closure scoreForB = gradeAssignment.curry(curve, "B")
04: Closure scoreForC = gradeAssignment.curry(curve, "C")
05: Closure scoreForD = gradeAssignment.curry(curve, "D")
06: Closure scoreForF = gradeAssignment.curry(curve, "F")

07: Test test = . . .
08: String grade
09: 
10: switch(test) {
11:   case scoreForA: grade = 'A'; break
12:   case scoreForB: grade = 'B'; break
13:   case scoreForC: grade = 'C'; break
14:   case scoreForD: grade = 'D'; break
15:   case scoreForF: grade = 'F'; break
16: }
I probably should have added a default: handler in my switch statement, but I omitted it for simplicity.

Except for incorrect use of functional terminology (curry) Groovy has done OK in this example.
Groovy gets lots of credit for being less verbose than Java.  I look at this differently:  I like when the language rewards me for doing the right thing.  Groovy has done just that with its cool switch statement. It awarded me with more compact and more readable code, it awarded me for thinking in more functional pattern matching terms.

No comments:

Post a Comment