When constructing your own types in Haskell, you can make your type support a particular behavior by making it an instance of the behavioral type class required. I’ll walk through each of these derivable behaviours and how they can help.
Eq gives your type a sense of equality amongst values of the same type. It allows you to use the == operator as it was intended, returning you a boolean.
dataEmployee=Employee{firstName::String,lastName::String,department::String}deriving(Eq)letmary=Employee{firstName="Mary",lastName="Jones",department="Finance"}letjohnIT=Employee{firstName="John",lastName="Smith",department="IT"}letjohnHR=Employee{firstName="John",lastName="Smith",department="HR"}-- would be Falsemary==johnIT-- would be TruejohnIT/=johnHR-- would be TruejohnHR==Employee{firstName="John",lastName="Smith",department="HR"}
From now on, == will do a comparison on the contents of the three strings in the Employee record for us.
In my personal experience when defining types, I would be out of my mind not to make them derive Show. Show allows a value of your type to be put into string format - very useful for debug situations.
dataEmployee=Employee{firstName::String,lastName::String,department::String}deriving(Show)letmary=Employee{firstName="Mary",lastName="Jones",department="Finance"}-- Will print "Employee { firstName = "Mary", lastName = "Jones", department = "Finance" }"putStr$(showmary)
Just for the printing value, you can see how Show is worth its weight in gold.
Read provides the reverse-service of what Show does. You’ll be able to take a type in its serialized format and re-construct a type from it. Again, rather useful in debug situations.
dataEmployee=Employee{firstName::String,lastName::String,department::String}deriving(Show,Read)letmaryStr="Employee { firstName = \"Mary\", lastName = \"Jones\", department = \"Finance\" }"-- mary will now be a constructed Employee letmary=read$maryStr::Employee
I’ve also used this to do user-input rather cheaply. Probably not quite a “production solution” though having your users enter type data directly.
Bounded will give your type a sense of the lowest and highest values achievable. You’ll be able to ask questions of the type to see what these corresponding values are.
Enum will give your type a sense of the predecessor and successor values. This is most important when dealing with ranges in using your type. Take a look at the following deck assembly. Without Bounded the list comprehensions would not be possible and this code would be a lot more verbose.
-- | Makes an ordered deck of cards makeDeck::[Card]makeDeck=[Cardvs|v<-[Ace..King],s<-[Heart..Spade]]
That’s derived instances for you anyway. They’re a great help when constructing your own types in Haskell. I think an important follow up to this blog post is being able to use these classes in conjunction with the instance keyword so that we can supply the implementation to the definition. Using the card example, we could supply an Eq and Show instance as follows.
dataCardSuit=Diamond|Heart|Club|Spade-- Provide eq implementationinstanceEqCardSuitwhereDiamond==Diamond=TrueHeart==Heart=TrueClub==Club=TrueSpade==Spade=True_==_=False-- Provide show implementationinstanceShowCardSuitwhereshowDiamond="Diamonds"showHeart="Hearts"showClub="Clubs"showSpade="Spades"
You can see here that it’s quite counter-productive to supply our own Eq implementation, but if we did have some funky rules on how we wanted equality operators to work it would be worth it. In the show implementation, I’ve tried to make the suits read a little more humanly. Around the card table, you would normally hear someone say “Do you have a 2 of clubs?” rather than “Do you have a 2 of club?”. The trailing “s” has been added in the show implementation. Neat.
Functor is applied to wrapper types. You’ll commonly see examples used with Maybe. You’ll use Functor when ever you need to supply an fmap implementation. Here is a simple example that creates a wrapper data type.
In a previous post, I had written about [Folding in Haskell](/2012/12/27/folding-in-haskell.html] which in itself is a very powerful tool. We spent a brief moment in that tutorial actually working through a problem and writing the folding process out long hand.
Well, scanning scanl and scanr does this for you! It shows you the reduction steps in the form of an array returned back to you.
Sometimes, Haskell’s syntax is so alien to read (to my eyes at least anyway). I’ve seen wide-spread use of the $ operator all over lots of people’s code and never really had a grasp on what it is/does. In the end, it’s really quite simple. The whitespace character has a very high precedence order, so when you’re using spaces the precedence order looks like this.
fabc=((fa)b)c
This is classified as being left-associative. In contrast, using the $ operator allows us to be right-associative. An example.
f$a$b$c=f(a(bc))
Looking at this, it’s starting to look very much how our programming languages are structured with function calls. We’re very right-associative. Because Haskell uses the white space character to denote left-associations, it takes a lot of parenthesis to make a complex state right-associative as you need to change the precedence order by hand. This is the true power of the $ function. We can use $ to free us from parenthesising everything, so that a transformation as below occurs.
putStrLn(shownum)putStrLn$shownum
This simple scenario doesn’t illustrate exactly how much $ will help us out. Here’s another slightly more complex scenario. It becomes clear here that $ is working to make our code more readable.
sum(take10(cycle[1,2,3]))sum$take10$cycle[1,2,3]
In character-space length they’re equivalent, but the second version looks less LISP-y. I guess this is what was being aimed at.
Seems I’m forever making card games in Haskell, so it only seemed right that I try and make a library of routines and data types that will get me going quicker. Don’t get me wrong, I intend on doing something serious with Haskell one day - I just seriously lack the chops to do so right now.
As with any development process, you as a developer should write unit tests. Not only is it good practice but it also gives you a repeatable base of executions to assure you that the last change you put in won’t break your masterpiece. Today I want to talk about the QuickCheck unit testing library for Haskell.
What are we testing?
To give you an idea of the playing field we’re on, I’ll just post some of the simple routines that I have done so far. First up is the data types that will help us represent a single playing card.
-- | Card values for a standard deck dataCardValue=Ace|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|Jack|Queen|Kingderiving(Show,Eq,Enum)-- | Possible card suits dataCardSuit=Heart|Diamond|Club|Spadederiving(Show,Eq,Enum)-- | A card dataCard=CardCardValueCardSuitderiving(Show,Eq)
A card has a suit and a value. Pretty straight forward. I could have made a type that wrapped an array of the Card type and called it Deck, but I’m happy just handling an array of Card. Now to build a deck and to shuffle it!
-- | Seeds a list of cards with a random value seedCards::StdGen->[Card]->[(Card,Int)]seedCardsg[]=[]seedCardsg(c:cs)=x:seedCardsngcswhere(seed,ng)=randomR(1,10000)g::(Int,StdGen)x=(c,seed)-- | Makes an ordered deck of cards makeDeck::[Card]makeDeck=[Cardvs|v<-[Ace..King],s<-[Heart..Spade]]-- | Makes a randomly shuffled deck of cards makeShuffledDeck::StdGen->[Card]makeShuffledDeckg=[x|c<-sorted,letx=fstc]wherecards=seedCardsgmakeDecksorted=sortBy(compare`on`snd)cards
When a deck is built with makeDeck the cards are ordered just like they are when you open a fresh deck of cards, so we need to shuffle them in order to make this game any fun! seedCards assigns a random value to each card that it is passed and then makeShuffledDeck saves the day by ordering by this random seed to give a shuffled deck.
That’s all pretty simple still and that’s where the “testable” parts stop. So, still the question: what are we testing? Well, I’m sure there are plenty of other scenarios, but for today’s purposes we’ll test the following:
Are there 52 cards in a deck made by makeDeck?
Are there still 52 cards in a deck after they’ve been processed by makeShuffledDeck?
Is the deck made by makeDeck not in the same order as the deck made by makeShuffledDeck?
Great. With these three scenarios in mind, here’s how easy it is to assert these facts using QuickCheck.
Hold on!100 tests? We only defined 3 tests though. How can this be? You’ll see that for the second and third tests actually have an anonymous function passed to them. Because both of these depend on a random number generator (to shuffle the deck), I’ve passed in mkStdGen’s integer that it maps to a generator from the function’s parameter list. QuickCheck grabbed hold of this and rather than just running 1 test, it went ahead and gave the anonymous function 100 random values. That’s much better coverage for what is seemingly the cost of defining the test as an anonymous method. Immediately you can see the power of unit testing with such a simple framework and how you can be productive relatively quickly.