Virtuous Programmer Adventures of an Autodidact

13Dec/100

Scala 2/4: Loops, Decisions and Tests

This is the second article in a series, to read the whole series go here.

This week I've worked on learning Scala's basic syntactic structures and how to write unit tests. To accomplish this I've implemented a solution to the fizzbuzz problem and written a couple of unit tests to demonstrate that it works.

For an overview of the fizzbuzz problem, go to wikipedia's page.

Conditionals, Loops and Other Constructs

Fizzbuzz.scala

Defining Methods

  1. object Fizzbuzz {
  2.   // Returns true if the given number is divisible by 3
  3.   def isfizz(num : Int) : Boolean = { num % 3 == 0 }
  4.  
  5.   // Returns true if the given number is divisible by 5
  6.   def isbuzz(num : Int) : Boolean = { num % 5 == 0 }
  7.  

The method definition syntax is in the format: def <method name>(<arg name> : <arg type>) : <result type> = <body>. You can see here that there is no 'return' statement in Scala. The value of any given block is the last statement executed in that block. This can lead to some confusion if you have very large methods with a lot of complex branching. The obvious solution is not to have very large methods. I generally start breaking up my methods if they start getting to be more than 12 lines long.

Scala is a statically typed language, which means that all of the variable/argument types are set at compile time. Fortunately it also uses Hindley-Milner type inference which will often guess the intended type of an argument/variable. Because of this the above code can be simplified as:

  1. object Fizzbuzz {
  2.   // Returns true if the given number is divisible by 3
  3.   def isfizz(num : Int) = num % 3 == 0
  4.  
  5.   // Returns true if the given number is divisible by 5
  6.   def isbuzz(num : Int) = num % 5 == 0
  7.  

As far as I'm able to tell, unlike in some systems that use Hindley-Milner, types are required when defining the arguments for a method. I don't really view this as a disadvantage, when I'm coding I get hopelessly confused if I don't have the intended type of my variables noted somewhere, there's no reason not to have it in a way that the compiler can catch my mistakes.

You can also see that when there's only one statement in the method there's no need for the curly braces around it. This can lead to significantly cleaner looking code when you just have a one liner.

If Statements and Exploring Your Options

  1.    def fizzbuzzSingle(num : Int) : Option[String] = {
  2.     if(num <= 0) None
  3.     else if(isfizz(num) && isbuzz(num)) Some("fizzbuzz")
  4.     else if(isfizz(num)) Some("fizz")
  5.     else if(isbuzz(num)) Some("buzz")
  6.     else Some(num.toString())
  7.   }
  8.  

If Statements

One of the ways the Scala team tried to make their language more accessible is to keep the syntax similar to Java where they didn't either simplify it or add functionality which needed new syntax. So anyone who is familiar with any of the Algol type programming languages will recognize the format of the conditional statements above.

Algebraic Data Types

What is less common is Scala's use of an algebraic type system. In short this means that there are types that wrap other types. In the above case the Option type can wrap an arbitrary types. I've used it to wrap a string, bit it could just as easily wrap an Int or a Window object. There are a myriad of reasons for using an algebraic type system. Here it allows me to indicate that an input is invalid (values less than 1) without throwing an exception.

When you are using a package that communicates failure through exceptions, it's often hard to know what exceptions to expect. By using the type system, if you know the type signature of your function/method, you can be sure what to expect as output from your function. When an invalid value is handed to a function using the Option type, it returns None. When it receives a valid value then it returns Some(<response>).

Strictly speaking Scala doesn't have Algebraic types, here for example None and Some are subclasses of the abstract class Option. But for practical purposes it's hard to tell the difference.

Comprehensions and Match

  1.    def fizzbuzzList(end : Int) : List[String] = {
  2.     for (num <- List.range(1, end + 1)) yield fizzbuzzSingle(num) match {
  3.       case Some(n) => n
  4.       case None => ""
  5.     }
  6.   }
  7.  

For Comprehensions

For comprehensions are similar to list comprehensions in other languages with the syntax: for <value> <- <list> if <boolean statement> yield <result>. This will go through each of the values in <list> and if the <boolean statement> returns true then yields the given <result>. The if <boolean statement> part is optional as in the above code.

Match/Case Statements

Scala's match/case statements are a generalization of the more common switch statement. The difference between them are that checking isn't just at a value level, but at a type level also. Here I'm using it to distinguish between the Some and None type, then I can extract the variable value in Some(n) and make use of it.

main

  1.    def fizzbuzzString(end : Int) : String = fizzbuzzList(end).mkString(" ")
  2.  
  3.   def main(args : Array[String]) = println(fizzbuzzString(20))
  4. }
  5.  

When a file contains an object with a main method, it will execute that method when it's associated class is called from Java.

Testing in Scala

TestFizzbuzz.scala

There are a number of unit test frameworks available for Scala. The one that comes with the system, SUnit, has been deprecated. While there isn't a replacement in the standard libraries, the most common recommendations are:

  • ScalaTest: A standard unit testing framework.
  • scalacheck: A test generation framework in the same style as Haskell's quickcheck.
  • specs: A Behavior-Driven-Design style of test framework.

For my purposes I chose scalacheck because it is well suited to testing the fizzbuzz problem, and I'm rather fond of quickcheck. In scalacheck you write assertions about the function that is tested instead of writing explicit test cases. Scalacheck then generates random data to verify those assertions.

Installing the Library

To install scalacheck, download the jar file (scalacheck-..-.*.jar) from scalacheck's download page and save it to Scala's lib directory, c:\scala\lib if you're continuing from last week. 

Importing Libraries

  1. import org.scalacheck._
  2. import Fizzbuzz._
  3.  

Import in Scala looks very similar to Import in Java except instead of using *, you use _ to indicate a wildcard.

Subclassing and Local Imports

  1.  
  2. object TestFizzbuzz extends Properties("fizzbuzzSingle") {
  3.   import Prop._
  4.  

Scala's subclassing and trait system is extraordinarily complex, but just subclassing from one other class is simple: object <object name> extends <super class>

If you only need to use an import in context to a single class or object, then you can import it inside that class or object.

A scalacheck Properties

  1. property("fizzbuzzSingle: < 1 is None") = forAll
  2.   { (num: Int) => (num < 1) ==> (Fizzbuzz.fizzbuzzSingle(num) == None) }
  3.  
  4.   property("fizzbuzzSingle: >= 1 is not None") = forAll
  5.   { (num: Int) => (num >= 1) ==> (Fizzbuzz.fizzbuzzSingle(num) != None) }
  6.  
  7.   property("fizzbuzzSingle: (mod 3) and not (mod 5) is Some(\"fizz\")") =
  8.     forAll (Gen.posNum[Int])
  9.       { (num: Int) => ((num % 3 == 0) && (num % 5 != 0)) ==>
  10.         (Fizzbuzz.fizzbuzzSingle(num) == Some("fizz")) }
  11. }
  12.  

Scalacheck has a lot of depth to it, but getting started is simple. A simple template is:

property("<property description>") = forAll { (<argument> : <type>) => (<limiting condition>) ==> (<assertion>) }

Working backwards, the assertion is a boolean statement that should be true so long as the limiting condition is true. The argument/type combination are the values that you want the system to generate randomly. As in a normal function definition, you can have as many argument/type pairs as you like, separated by commas.

My Experience

Overall I've found working out the fizzbuzz problem in Scala to be interesting and, the language didn't spend much time getting in the way. The two major caveates to that are:

  1. The state of flux of the unit test system, it'd be nice if they'd pick one to include in the package.
  2. When the code is compiled, there are an unusually large number of class files because of the anonymous functions that tend to crop up in functional code (14 if you include the test code).

Resources

Posted by Frank Berthold

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.