ScalaUtils: Simply Productive

Just Released - ScalaUtils 2.1.5!

The ScalaUtils library is focused on constructs related to quality that are useful in both production code and tests. Although ScalaTest is tightly integrated with ScalaUtils, you can use ScalaUtils with any Scala project and any test framework. The ScalaUtils library has no dependencies other than Scala itself.

The === and !== operators

ScalaUtils provides a powerful === operator (and its complement, !==) that allows you to

  • Customize equality for a type outside its class
  • Get compiler errors for suspicious equality comparisons
  • Compare numeric values for equality with a tolerance
  • Normalize values before comparing them for equality

For example, you can compare strings for equality after being normalized by forcing them to lowercase by customizing Equality explicitly, like this:

    import org.scalautils._
    import TripleEquals._
    import StringNormalizations._
    import Explicitly._

    ("Hello" === "hello") (after being lowerCased) // true
    

You can also define implicit Normalization strategies for types and access them by invoking norm methods:

    import NormMethods._

    implicit val strNormalization = lowerCased

    "WHISPER".norm                                // "whisper"
    "WHISPER".norm === "whisper"                  // true
    

The “after being lowerCased” syntax shown previously is provided by ScalaUtils' Explicitly DSL, which allows you to specify Equality explicitly. You can also define custom Equalitys implicitly:

    implicit val strEquality =
      decided by defaultEquality[String] afterBeing lowerCased

    "Hello" === "hello"                           // true
    "normalized" === "NORMALIZED"                 // true
    

You can compare numeric values for equality with a Tolerance, like this:

    import Tolerance._

    2.00001 === 2.0 +- 0.01                       // true
    

Or you could use TolerantNumerics define an implicit Equality[Double] that compares Doubles with a tolerance:

    import TolerantNumerics._

    implicit val dblEquality = tolerantDoubleEquality(0.01)

    2.00001 === 2.0                               // true
    

A compiler error for an equality comparison that would always yield false looks like:

    import TypeCheckedTripleEquals._

    Some("hi") === "hi"
    error: types Some[String] and String do not adhere to the type constraint
    selected for the === and !== operators; the missing implicit parameter is
    of type org.scalautils.Constraint[Some[String],String]
              Some("hi") === "hi"
                         ^
    

Or and Every

ScalaUtils provides an “Either with attitude” named Or, which gives you more convenient chaining of map and flatMap calls (and for expressions) than Either and, when, combined with Every, enables you to accumulate errors. Every is an ordered collection of one or more elements. An Or is either Good or Bad. An Every is either One or Many. Here's an example of accumulating errors with Or and Every:

import org.scalautils._
import Accumulation._

case class Person(name: String, age: Int)

def parseName(input: String): String Or One[ErrorMessage] = {
  val trimmed = input.trim
  if (!trimmed.isEmpty)
    Good(trimmed)
  else
    Bad(One(s""""${input}" is not a valid name"""))
}

def parseAge(input: String): Int Or One[ErrorMessage] = {
  try {
    val age = input.trim.toInt
    if (age >= 0) Good(age) else Bad(One(s""""${age}" is not a valid age"""))
  }
  catch {
    case _: NumberFormatException =>
      Bad(One(s""""${input}" is not a valid integer"""))
  }
}

def parsePerson(inputName: String,
      inputAge: String): Person Or Every[ErrorMessage] = {

  val name = parseName(inputName)
  val age = parseAge(inputAge)
  withGood(name, age) { Person(_, _) }
}

Here are some examples of parsePerson in action:

parsePerson("Bridget Jones", "29")
// Result: Good(Person(Bridget Jones,29))

parsePerson("Bridget Jones", "") // Result: Bad(One("" is not a valid integer))
parsePerson("Bridget Jones", "-29") // Result: Bad(One("-29" is not a valid age))
parsePerson("", "") // Result: Bad(Many("" is not a valid name, "" is not a valid integer))

Or offers several other ways to accumulate errors besides the withGood methods shown in the example above. See the documentation for Or for more information.

And more (but not much more)...

ScalaUtils also includes a TimesOnInt trait that allows you to perform side-effecting loops a specified number of times, like this:

import TimesOnInt._

3 times println("hello ") // Output: hello hello hello

You can also define an alternate String forms for types using Prettifiers and create extractors for Throwables via the Catcher factory.

And that's it: ScalaUtils is a small, very focused library. Why not give it a try? Just Download ScalaUtils, then visit the Quick Start page.

ScalaUtils is brought to you by Bill Venners, with contributions from several other folks. It is sponsored by Artima, Inc.
ScalaTest is free, open-source software released under the Apache 2.0 license.

Copyright © 2009-2013 Artima, Inc. All Rights Reserved.

artima