Functional Programming Unit Testing – Using Type Classes
I wanted to take a brief sidebar from the refactoring conversation that I’ve been having in the past couple of posts, and focus on QuickCheck again. In the next post, I’ll get back to refactoring with HLint. In this post, I want to talk about using type classes to implement operators to use for QuickChecks property-based tests.
Let’s get caught up to where we are today:
- Part 1 - xUnit Frameworks - HUnit
- Part 2 - Property-Based Tests - QuickCheck
- Part 3 - QuickCheck + xUnit Tests Together
- Part 4 - Code Coverage
- Part 5 – Keeping Things Pure
- Part 6 – Monadic Abstractions
Utilizing Type Classes
In the previous post about, “How Would the CLR be Different?”, I stated that I wished the CLR made it easier for higher-kinded polymorpishm to allow for better type classes. But, let’s step back for just a second to talk about how type classes can help us.
Let’s say for example, that we’d like to test for approximate equality for floating-point numbers. Floating point numbers cannot reliably be compared for exact equality. Instead, the algorithm that is used to check for approximate equality is as follows:
Where we take the absolute value of the difference between the two numbers and compare the result to ensure that it is smaller than epsilon (a rather small number). But, I’d rather have the ability to write the code the way that it makes sense to the reader instead of all the semantics of the approximate equality. Let’s write a test for the way that it should look using an operator as an implementation of approximate equality.
prop_average :: [Double] -> Bool
prop_average xs =
(average xs * fromIntegral (length xs)) =~= sum xs
-- Implementation of average
average :: (Fractional a) => [ a ] -> a
average [] = 0
average xs = sum xs / fromIntegral (length xs)
Our test makes relative sense that the average of a list multiplied by the length should approximately equal the sum of said list. This should always hold true for our tests. But, how do we write that operator? With the beauty of type classes of course. First, let’s look at the implementation of general equality inside Haskell using a type class.
(==) :: a -> a -> Bool
(/=) :: a -> a –> Bool
This allows us to implement the equality for any given type that we create or use the default implementation given. Let’s take this approach to create an ApproxEq type class so that we can implement it for our comparisons.
(=~=) :: a -> a -> Bool
(=/~=) :: a -> a –> Bool
We’ve now implement both an approximate equality and approximate inequality function inside our ApproxEq type class. Now to actually use this, we need to create an implementation using double floating-point precision for our average tests. An example might look something like the following:
x =~= x' = abs (x-x') < epsilon
where epsilon = 0.0000001 –- Could be smaller
x =/~= x' = not $ x =~= x')\
Now, when we compile our prop_average, it should now work as expected where I’m checking for approximate equality. We can wire up our property checks in batch form and run as the following:
import Test.QuickCheck.Batch
options = TestOptions
{ no_of_tests = 200,
length_of_tests = 1,
debug_tests = False}
main :: IO ()
runTests "average properties" options
[ run prop_average ]
And when we run through the GHCi console, we get the following results:
average properties : . (200)
We now see that we pass 200 tests, which is something we expected. Moving on over to F#, is this something we can apply? The answer is yes!
Type Classes in F#
I’ve had a lot of thoughts lately around type classes and their importance in the F# language. Unfortunately, there aren’t clean ways to do this. Instead, as Kurt Schelfthout discovered in his post about “A Poor Man’s Type Class”, it can be done, but always the prettiest solution. Let’s go ahead and go for the same ideas as above in the Haskell code in F# and see how it can be accomplished.
The idea, as stated above is to create a test and associated implementation to use an operator to check for approximate equality. Here is how I’d like to write my code:
((List.average xs) * float (List.length xs)) =~= List.sum xs
[<Fact>]
let test_prop_average() =
check FsCheckExtensions.config prop_average
First, let’s define the interface much as we did above for our type class above to check for both approximate equality and inequality:
abstract member approxequal : 'a -> 'a -> bool
abstract member approxinequal : 'a -> 'a –> bool
Now, we need the ability to store the associations of the type instances of our given interface. This allows us to query a table to see whether one has been implemented or not.
[<Sealed>]
type ApproxEqAssociations private() =
static let associations = new System.Collections.Hashtable()
static member Add<'a>(approxEqual : IApproxEq<'a>) =
associations.Add(typeof<'a>, approxEqual)
static member Get<'a>() =
let a = associations.[typeof<'a>]
match a with
| null ->
failwithf "Type %s does not have an implementation of IApproxEq"
(string (typeof<'a>))
| :? IApproxEq<'a> -> a :?> IApproxEq<'a>
| _ ->
failwithf "Type %s has an incorrect implementation of IApproxEq"
(string (typeof<'a>))
What we’ve implemented here is a static class that will let us both add and get instances of our IApproxEq type instances based upon a given incoming type. This gives us the capability now of creating instances of our ApproxEq class and and adding it to the associations table such as this:
type DoubleApproxEqual() =
let epsilon = 0.000001
let approxEq x x' = abs(x - x') < epsilon
interface IApproxEq<float> with
member this.approxequal x x' = approxEq x x'
member this.approxinequal x x' = not (approxEq x x')
ApproxEqAssociations.Add(new DoubleApproxEqual())
Once the class has been created and then added to the association table, we can then retrieve it any time we need through the use of our ApproxEqAssociations.Get method. Let’s not stop there, because now I want to create some operators that will then act upon this association to allow us to check for approximate equality. Those are defined below:
let (=~=) x x' = ApproxEqAssociations.Get().approxequal x x'
let (=/~=) x x' = ApproxEqAssociations.Get().approxinequal x x'
Now that everything is defined, it should just work through our handy FSI command prompt such as the following:
-Ok, passed 100 tests.
And there you have it, we have type classes that can help improve the design of our code. Is it checked at compile time whether we have an instance or not? The answer, is unfortunately, no. That’s why our tests are of greater importance to verify the behavior of these type classes.
Conclusion
As you can see, we have the ability to define type classes in such a way as to abstract such things as checking for approximate equality. This allows us to write the code the way it should be, instead of worrying about underlying complexity that is checking for such a thing as approximate equality. Using techniques such as this also allows us to generalize our functions in such a way that we get maximum reuse.
Next time, we’ll move back to our refactoring topics and cover refactoring and cleaning our code using HLint.