Playing with Functional Programming

The topic will be functional programming with emphasis on Haskell. I'm also writing my own language called Mikan and hope to share progress here. So let's have fun!

Wednesday, February 2, 2011

Homebrew for Haskell!

It's now quite easy to get Haskell and LLVM to install and play together on Mac OS X! Following some of the advise from here, you first need to remove your existing Haskell Platform and packages:

> sudo /Library/Frameworks/GHC.framework/Tools/Uninstaller
> rm -rdf ~/.ghc
> rm -rdf ~/.cabal

If you don't already have Homebrew installed, go ahead and do that.

Now for the fun! (Although pour yourself a cup of coffee as this takes a while...)

> brew install llvm --universal

Note that the "--universal" option is critical! If you don't specify this, the LLVM Haskell library will not build. Note: you may also wish to specify "--with-clang" if you'd like to simultaneously build clang as well.

> brew install haskell-platform

> cabal update

> cabal install llvm

> ghci

Prelude> :m LLVM.Core
Prelude LLVM.Core>

Viola!

Sunday, August 29, 2010

Installing Haskell Bindings to Readline on Mac OS X with Homebrew

Since this caused me a bit of grief, I thought I'd document the process. My objective: to have "cabal install readline" work on Mac OS X. If you just run it after a fresh install of the Haskell Platform, no pony.

Here are some relevant link:

  • A discussion on StackOverflow pointing out the correct cabal install mechanism
  • A GHC ticket that points out the readline script is not using standard cabal flags
  • The Homebrew package management system for Mac OS X -- highly recommended
Using homebrew, you can quickly install readline by typing "brew install readline" at the prompt. There is a note that this homebrew formula is "keg-only" which means it is not linked up into usr/local as other packages are to prevent conflict with the BSD readline library that comes with OS X.

So, all you have to do is direct link into where the actual GNU readline code is stored when running cabal install:

$ cabal install readline --configure-option=--with-readline-libraries="/usr/local/Cellar/readline/6.0/lib" --configure-option=--with-readline-includes="/usr/local/Cellar/readline/6.0/include"

Anyhow, this worked for me and hopefully can save someone else some runaround time and headache.

Cheers,

Michael

Saturday, May 22, 2010

Cabal Installing Haskell LLVM Module on Mac OSX

I got stars in my eyes when I saw the LLVM module for Haskell developed by Bryan O’Sullivan and Lennart Augustsson. Think of the possibilities!

Just as a bit of clarification, there is also a recent buzz around the net related to LLVM and GHC/Haskell due to the LLVM code-gen functionality integrated into GHC by David Terei in his thesis. Although that is definitely very cool, it's different than the LLVM module.

There are a few posts by Bryan and Lennart that discuss the LLVM module here (which I'm listing for completeness and also for my own reference):
Unfortunately, despite my initial excitement, I wasn't able to get the LLVM module to ever install via cabal ... until now that is!

I thought I'd write a post on how I did it in case anyone else is having similar issues.

First, you need to install LLVM. I took some cues from the install guide for unladen swallow (the fast Python implementation on LLVM and using a jit compiler that Google is sponsoring) and also from the LLVM getting started page.

Specifically, I installed LLVM 2.7 and also the gcc front end (not required to my knowledge but looked interesting/useful). Here are the commands I used:

> wget 'http://llvm.org/releases/2.7/llvm-gcc-4.2-2.7-x86_64-apple-darwin10.tgz'
> wget 'http://llvm.org/releases/2.7/llvm-2.7.tgz'
> tar xzf llvm-gcc-4.2-2.7-x86_64-apple-darwin10.tgz

Once you've untarred the gcc binaries, move them to the location you want to install them at. Be careful with just dropping folders onto existing folder hierarchies on Mac -- this has an effect of replacing the target folders as opposed to adding the contents to the existing tree (a discovery learned through tears I can tell you). Continuing:

> tar xzf llvm-2.7.tgz
> mkdir llvm_build
> cd llvm_build
> ../llvm-2.7/configure --prefix={prefix path if not installing to default} --with-llvmgccdir={path to llvm-gcc binaries}

Note: if you'd like the O'Caml bindings, add "--with-ocaml-libdir={path to your ocaml library}". For example, the values for me were:

* --prefix=/Users/{uname}/.local --with-llvmgcdir=/Users/{uname}/.local --with-ocaml-libdir=/usr/local/lib/ocaml

If you're installing to the standard locations and installed the gcc front end in a standard area on the path, you don't need the above configuration flags. I decided to install to a custom location and so required the flags myself.

At this point you are ready to compile LLVM. I had successfully downloaded and built llvm many times before and confirmed the tools worked (llc, lli, etc.). However, I could never succeed in getting the Haskell LLVM module to build. The big breakthrough came from this e-mail which indicates a need to declare the variable "UNIVERSAL" as follows:

> UNIVERSAL=1 gmake -j4

This will take a while to compile. The 4 after the j flag indicates the number of processors to use. Once compilation completes, you need to:

> gmake install
> cabal install llvm

and you're done!

Tuesday, March 23, 2010

Something Fishy in Haskell!

Well, here it is. The amazing! The stupendous! The … erm … fishy! That’s right, it’s “Go Fish!”, the amazing and riveting card game we all played as children! (Suzy, do you have any eights? Nope, GO FISH!)

Welcome to the first in a series of blog post using games to explore functional programming code. In this first code, I’ve programmed up “Go Fish” as a multi-player game. Play takes place on the console in this version and there is currently no computer artificial intelligence (AI) so arguably as a game, this leaves much to be desired. However, it has merit in terms of allowing me to get practice writing functional code. I also thought this might be worthwhile sharing to compare insights … so … here goes!

This will be implemented as a single literate haskell module called “GoFish.lhs”. First, let’s load the required functions from other modules.

> import Random(randomR, StdGen, mkStdGen, newStdGen)
> import Data.Map(Map, fromList, (!), empty, insert, keys)
> import Data.List(foldl', partition, group, sort, sortBy,
>   intercalate)
> import Data.Char(isSpace, toLower)

One thing I like to do is explicitly load functions from other modules. This preference is heavily influenced by Python’s “from import ”. This type of importing makes it crystal clear what functions from what modules are being used. It is also useful when trying to follow someone else’s code (or your own code several months down the road). Any functions not appearing in the above import list are either defined in the module or part of Haskell’s standard Prelude. The Prelude is essentially a module which is always imported and contains the most useful core functions. A listing of what’s in Haskell’s Prelude can be found here.

I’ve been studying F# recently and have grown to love the forward pipe operator as well as the forward composition order. These operators better support a left to right data flow which is how my brain wants to work. It’s totally cool that Haskell has the power to define these useful operators “on the fly”.

As you can see, the pipe operator (|>) is infix left while the forward function composition operator (.>) binds to the right.

> infixl 0 |>
> infixr 9 .>

And here are the definitions:

> (|>) :: a -> (a -> b) -> b
> (|>) x f = f x
> (.>) :: (a -> b) -> (b -> c) -> a -> c
> (.>) f g = g . f

Now, let’s get to the cards! I have a data type for card suit and card value and a final data type just for Cards. I’ve derived several type classes for convenience. If you’re not familiar with these, we get free type class implementations through this mechanism (without having to write code). For example, deriving from the type class, Show, allows us to use the “show” function on the type and turn it into a string.

> data Suit = Spades | Clubs | Hearts | Diamonds
>   deriving (Eq, Show, Enum, Ord)
> data CardValue = Ace | V2 | V3 | V4 | V5 | V6 | V7
>   | V8 | V9 | V10 | Jack | Queen | King
>   deriving (Eq, Enum, Ord)

I’ve implemented a custom value of Show for CardValue in order to get string representations more to my liking for each card value.

> instance Show CardValue where
>   show Ace = "Ace"
>   show V2 = "2"
>   show V3 = "3"
>   show V4 = "4"
>   show V5 = "5"
>   show V6 = "6"
>   show V7 = "7"
>   show V8 = "8"
>   show V9 = "9"
>   show V10 = "10"
>   show Jack = "Jack"
>   show Queen = "Queen"
>   show King = "King"

The following two functions are used to manipulate and “standardize” input strings. Note that in the definition below, dropSpace is written in the so-called “point free” format where arguments are not explicitly shown. Because of the potential confusion, I’ve also chosen to write the type signature. The strip function also demonstrates the usage of my custom “forward pipe” implementation. Thus, to strip white space from a string, we take the string, drop spaces from the front, reverse the string, drop spaces from the front again (which is now the rear since the string is reversed), and finally reverse the string so that it is “right way forward” again.

> strip :: String -> String
> strip s =
>   let dropSpace :: String -> String
>       dropSpace = dropWhile isSpace
>   in  s |> dropSpace |> reverse |> dropSpace |> reverse

The fixStr function takes a string and strips and lower cases it to make comparisons easier.

> fixStr :: String -> String
> fixStr s = s |> strip |> map toLower

The purpose of strToCard is to match potential strings and return card values from that. There is probably a more elegant way to do this using the Read type class but this seemed straight forward enough.

> strToCard :: String -> CardValue
> strToCard s =
>   case fixStr s of
>       "ace" -> Ace
>       "jack" -> Jack
>       "queen" -> Queen
>       "king" -> King
>       "2" -> V2
>       "3" -> V3
>       "4" -> V4
>       "5" -> V5
>       "6" -> V6
>       "7" -> V7
>       "8" -> V8
>       "9" -> V9
>       "10" -> V10
>       otherwise -> error "unknown string"

Finally, the card and a few helper functions:

> data Card = Card CardValue Suit
>   deriving (Eq, Ord)
> value :: Card -> CardValue
> value (Card v _) = v
> suit :: Card -> Suit
> suit (Card _ s) = s
> instance Show Card where
>   show (Card v s) = show v ++ " of " ++ show s
> type Deck = [Card]
> type ShuffledDeck = Deck
> type Hand = Deck
> type Name = String
> type Books = [[Card]]

The rules I looked up on the net for “Go Fish” talked about matching “books” of 4 of the same card value so I’ve used that same terminology here.

A player datatype will contain the player’s hand and any matched books thus far. The functions “hand”, “addCardToHand”, and “books” are convenience functions to make it easier to manipulate the Player datatype.

> data Player = Player Hand Books
>   deriving (Eq, Show)
> hand :: Player -> Hand
> hand (Player h _) = h
> addCardToHand :: Card -> Player -> Player
> addCardToHand c p =
>   let h = hand p
>       h' = c : h
>       bks = books p
>   in  Player h' bks
> books :: Player -> Books
> books (Player _ bs) = bs

Finally, a few more convenient type synonyms:

> type PlayerMap = Map Name Player
> type Message = String
> type PlayerNames = [Name]
> type PlayerState = (Bool, Message, PlayerMap)
> data GameState = ContinueTurn | NextPlayer
>   | GameOver deriving (Eq, Show)

Below, I’m using a Haskell list comprehension to build a pack of cards. Because I had the CardValue inherit from Enum (Enumeration), we get these great functions “enumFrom” which allow me to iterate through the potential (finite) card values and suits without having to write any custom code. Brilliant!

> deck :: Deck
> deck =
>   [Card v s |
>       v <- enumFrom Ace,
>       s <- enumFrom Spades]

Pop is a convenience function used in the shuffle function. Can you guess what shuffle does? Actually, what might not be immediately apparent is the method Haskell uses to perform random number generation. A standard random number generator (StdGen) type is needed. The variable “rng” stands for “random number generator”.

> -- Return list with item at idx removed
> --    idx must be one-based (i.e., first item is 1)
> pop :: Int -> [a] -> (a, [a])
> pop idx xs =
>   if null xs
>       then error "cannot pop a null list"
>       else
>           let len = length xs
>               n = idx `mod` (len + 1)
>           in  (xs !! (n - 1), 
>                   take (n - 1) xs ++ drop n xs)
> shuffle :: StdGen -> Deck -> (ShuffledDeck, StdGen)
> shuffle rng d = 
>   let iterPick rng d shuffled =
>           if null d
>               then (shuffled, rng)
>               else
>                   let (n, rng') = 
>                           randomR (1, length d) rng
>                       (nextCard, d') = pop n d
>                   in  iterPick rng' d' $
>                           nextCard : shuffled
>   in  iterPick rng deck []

Deal takes a deck of cards and deals five to each player.

> numCardsToDeal :: Int
> numCardsToDeal = 5
> deal :: Deck -> [Name] -> (Deck, PlayerMap)
> deal deck_ names =
>   let f (d, pm) n = 
>           (drop numCardsToDeal d,
>               insert n
>                   (Player (take numCardsToDeal d) [])
>                   pm)
>   in  foldl' f (deck_, empty) names

The function “groupBy” is actually a standard function available from Data.List. However, it didn’t do what I wanted it to. Specifically, I was hoping groupBy would group all like terms in a list together. Instead, it groups like “continuous” terms together. In other words, group by will only keep grouping until it runs into something outside of the group, then it will start a new group. The “groupBy’” function I wrote below has the desired effect which is to group all terms in the list that meet a criterion together.

> -- a different form of groupBy using partition
> groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
> groupBy' f [] = []
> groupBy' f (x:xs) =
>   let (matched, unmatched) = partition (f x) xs
>       gr = x : matched
>   in  if null unmatched
>           then [gr]
>           else [gr] ++ groupBy' f unmatched

The functions coming up below are used to perform specific game actions during the “Go Fish” card game turn. The function “askCards” simulates a player asking another player if they have any of a specific card value.

> -- Asker checks if Target Player has any cards of a Certain Rank
> askCards ::
>   Name -> CardValue -> Name -> PlayerMap -> PlayerState
> askCards asker val target pm =
>   let askingPlayer = pm ! asker
>       targetPlayer = pm ! target
>       hAsker = hand askingPlayer
>       hTarget = hand targetPlayer
>       matchingCards =
>           [Card val s | s <- enumFrom Spades]
>       (matched, hTarget') =
>           partition (`elem` matchingCards) hTarget
>       targetHadCards = length matched > 0
>       msg = if targetHadCards
>           then
>               let numCards = length matched
>                   plural = if numCards > 1
>                       then "s"
>                       else ""
>               in  asker ++ " got " ++ show numCards
>                       ++ " " ++ show val ++ plural
>                       ++ " from " ++ target
>                       ++ " ... Continue!"
>           else
>               target ++ " doesn't have any "
>                   ++ show val ++ "s, " ++ "Go Fish!"
>       askingPlayer' =
>           let hnd = hAsker ++ matched
>               bks = books askingPlayer
>           in  Player hnd bks
>       targetPlayer' =
>           Player hTarget' (books targetPlayer)
>       pm' = pm 
>             |> insert asker askingPlayer'
>             |> insert target targetPlayer'
>   in  (targetHadCards, msg, pm')

The function “getValidResponse” is used to continually prompt the user until a valid string is returned.

> getValidatedResponse :: 
>   (String -> Bool) -> String -> String -> IO String
> getValidatedResponse f prompt onFail = do
>   putStrLn prompt
>   response <- getLine
>   if f response
>       then 
>           return $ strip response
>       else do
>           putStrLn onFail
>           getValidatedResponse f prompt onFail

The function “checkForBooks” is used to check if a player has a matching set of four of one card value in their hand.

> checkForBooks :: Player -> (Bool, Message, Player)
> checkForBooks p =
>   let cs = hand p
>       valGrs =
>           let f = \c1 c2 -> (value c1) == (value c2)
>           in  groupBy' f cs
>       grLens = map length valGrs
>       lenVals = zip grLens valGrs -- [(Int, [Card])]
>       (bks, remainder) =
>           partition (\(len, _) -> len == 4) lenVals
>       bks' = map snd bks
>       bkVals = map (\cs -> cs |> head |> value) bks'
>       remainder' = map snd remainder
>       anyBks = length bks' > 0
>       msg =
>           if anyBks
>               then "You've matched " ++ 
>                       show (length bkVals) ++ " books: "
>                       ++ show bkVals
>               else "No books matched..."
>   in  (anyBks, msg,
>           Player (concat remainder') (bks' ++ books p))

The function unique groups all in a given list together and takes the head of each group so that there is only one of each kind of element in the list.

> unique :: (Eq a) => [a] -> [a]
> unique xs = xs |> group |> map head

The “announceWinner” helper function counts the number of books matched by each player to yield the final scores.

> announceWinner :: PlayerMap -> IO ()
> announceWinner pm = do
>     let ks = keys pm
>         nBks = map (pm !) ks |> map books |> map length
>         score = zip ks nBks
>     putStrLn $ "Final score:\n\t" ++ show score

The following two convenience functions prompt the active player as to which other player they should target to trade cards and prompt the active player to ask what kind of card they want.

> askForTarget :: Name -> [Name] -> IO Name
> askForTarget playerName validNames = do
>   let msg = "* Who will you ask for cards? "
>           ++ (show validNames)
>       errMsg = "... that cat doesn't exist, try again"
>   getValidatedResponse (`elem` validNames) msg errMsg
> askForValue :: Name -> [Name] -> IO Name
> askForValue target validRanks = do
>   let msg = "* What will you ask " ++ target
>           ++ " for? " ++ show validRanks
>       errMsg = "...Not a valid rank or you don't hold\
>               \ that card"
>   getValidatedResponse (`elem` validRanks) msg errMsg

The function isGameOver checks if conditions for Game Over have been reached: namely, if a player has matched all the cards in their hand.

> isGameOver :: Name -> PlayerMap -> Bool
> isGameOver playerName pm =
>     if (playerName |> (pm !) |> hand |> length) == 0
>         then True
>         else False

The function “goFish” simulates having to fish for a card out of the main deck.

> goFish :: 
>   Name -> Deck -> CardValue -> PlayerMap ->
>       (GameState, Message, Deck, PlayerMap)
> goFish playerName playDeck val pm =
>   let playDeck' = tail playDeck
>       fishedCard = head playDeck
>       player = 
>           addCardToHand fishedCard (pm ! playerName)
>       (hadBooks, bkMsg, player') = checkForBooks player
>       pm' = insert playerName player' pm
>   in  if val == (value fishedCard)
>           then -- fished card is what you wanted!
>               let msg = "\nYou caught a "
>                       ++ show fishedCard
>                       ++ ", just what you were fishing "
>                       ++ "for!\n" ++ bkMsg
>               in  if isGameOver playerName pm'
>                       then (GameOver, msg,
>                               playDeck', pm')
>                       else (ContinueTurn, msg,
>                               playDeck', pm')
>           else -- fished card wasn't what you wanted!
>               let msg = "\nYou caught a " ++
>                       show fishedCard ++
>                       ", not what you wanted..." ++
>                       "\nNext cat up!"
>               in  (NextPlayer, msg, playDeck', pm')

The function “allHands” is a convenient debugger function used to show the value of all player’s hands. It should only be used for debugging purposes.

> allHands :: PlayerMap -> IO ()
> allHands pm = do
>   let ks = keys pm
>       ps = map (pm !) ks
>       vs = map (hand .> map value .> sort) ps
>       sortFun = \(k1, _) (k2, _) -> compare k1 k2
>       kvs = sortBy sortFun $ zip ks vs
>   putStrLn $ show kvs

Finally, “play’”, “play”, “playerNames”, and “main” are all used to implement the actual game and set up useful defaults so one can just get started without having to define players.

> play' :: PlayerNames -> Deck -> PlayerMap -> IO ()
> play' ns playDeck pm = do
>   let playerName = head ns
>       validNames = tail $ ns
>       yourHand = playerName |> (pm !) |> hand |> sort
>       yourRanks = map value yourHand
>       validRanks = yourRanks |> unique |> map show
>       hbar = take 40 $ repeat '-'
>   putStrLn $ hbar ++ "\nCurrent Cat: " ++ playerName
>       ++ "\n" ++ hbar
>   putStrLn $ "Your Hand: "
>       ++ (intercalate ", " $ map show yourRanks)
>   -- allHands pm
>   target <- askForTarget playerName validNames
>   valStr <- askForValue target validRanks
>   let val = strToCard valStr
>       (gotCardFromTarget, msg, pm') =
>           askCards playerName val target pm
>       nextPlayers =
>           target : filter (\n -> n /= target) ns
>   -- allHands pm'
>   putStrLn msg
>   if gotCardFromTarget
>       then do -- got desired card from target
>           -- something seems wrong here:
>           let (hadBooks, bkMsg, player) =
>                   checkForBooks (pm' ! playerName)
>               pm2 = insert playerName player pm'
>           -- allHands pm2
>           putStrLn bkMsg
>           if (player |> hand |> length) == 0
>               then announceWinner pm2
>               else play' ns playDeck pm2
>       else -- GoFish!
>           if null playDeck
>               then play' nextPlayers playDeck pm'
>               else
>                   let (gameState, msg, playDeck', pm2) =
>                           goFish playerName playDeck
>                               val pm'
>                   in  case gameState of
>                           GameOver -> do
>                               putStrLn msg
>                               -- allHands pm2
>                               announceWinner pm2
>                           ContinueTurn -> do
>                               putStrLn msg
>                               -- allHands pm2
>                               play' ns playDeck' pm2
>                           NextPlayer -> do
>                               putStrLn msg
>                               -- allHands pm2
>                               play' nextPlayers
>                                   playDeck' pm2
> play :: PlayerNames -> IO ()
> play ns = do
>     rng <- newStdGen
>     let ns' = map strip ns
>         (shuffledDeck, rng') = shuffle rng deck
>         (playDeck, pm) = deal shuffledDeck ns'
>     play' ns' playDeck pm

As you read my blog, you’ll probably learn to expect this. It turns out that the players are all cats (except for Michael). Come now, haven’t you wanted to play go fish with cats?

> playerNames :: [Name]
> playerNames = ["Moonlight", "Mew", "Michael", "Cali"]

From ghci, you can merely type “main” to start the program and try this out!

> main :: IO ()
> main = do
>     play playerNames

Sunday, March 21, 2010

The New Haskell Platform is Out!

I was excited to see today that the new Haskell Platform containing version 6.12.1 of the (Glorious) Glasgow Haskell Compiler (GHC). And it truly is glorious since GHC now supports unicode IO!

Anyhow, without further ado, download it here!

Monday, March 16, 2009

Has this happened to you...?

OK, so this is twice now in one week, so I'm curious if this happens to anyone else:

A: "Hey, man, I'm learning a new computer language called Haskell!"
B: "Really? That's interesting because my company was just looking into using a new software package written in Pascal."
A: "Um, no not Pascal. Haskell. Haskell with an 'H'..."
B: "Oh... never heard of it."

The other fun one I get is:

A: "Hey, I'm learning about functional programming!"
B: "Functions? Oh yeah, I use functions. They're great!"
A: "..."

I think we still need more "rah rah rah" on the functional programming and Haskell advocacy side... On a positive note, maybe Pascal is coming back. (That is positive, right?)

Saturday, February 28, 2009

A More Elegant Solution for "Keyword Arguments" in Haskell?

In my last post, I showed a way to use "dummy" types (a "description type" called Desc) to annotate a function's arguments similar to the keyword arguments used in Python.

However, as I began to think about it more, I began to wonder if I was missing the point of the wonderful elegance of the Haskell type system. Putting it another way, I think there's a more elegant solution than the one proposed in my last post:
Haskell Code:

-- ClarityTake2.hs
type City = String

sanFrancisco :: City
sanFrancisco = "San Francisco, CA"

denver :: City
denver = "Denver, CO"

type CatName = String

moonlight :: CatName
moonlight = "Moonlight"

data From a = From a deriving (Show)

data To a = To a deriving (Show)

data Who a = Who a deriving (Show)

fly :: Who CatName -> From City -> To City -> String
fly (Who n) (From c1) (To c2) = n ++ " flies from " ++ c1 ++ " to " ++ c2

data Travel t1_who t2_place = Travel {
who :: t1_who,
from :: t2_place,
to :: t2_place
}

fly2 :: Travel CatName City -> String
fly2 t = (who t) ++ " flies from " ++ (from t) ++ " to " ++ (to t)

main :: IO ()
main = do
putStrLn $ fly (Who moonlight) (From denver) (To sanFrancisco)
putStrLn $ fly2 (Travel {who=moonlight, from=denver, to=sanFrancisco})
-- main =>
-- Moonlight flies from Denver, CO to San Francisco, CA
-- Moonlight flies from Denver, CO to San Francisco, CA

So in the code above, we use the type system itself to make it clear what we're trying to do. Why does this or should this work? Well, if one is using the excellent ghci (Glasgow Haskell Compiler interpreter), then one only needs to type ":i " and you can get a nice type signature back. Consider the following interactive ghci session:

GHCi, version 6.10.1: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> :l ./ClarityTake2.hs
[1 of 1] Compiling Main ( ClarityTake2.hs, interpreted )
Ok, modules loaded: Main.
*Main> :i fly
fly :: Who CatName -> From City -> To City -> String
-- Defined at ClarityTake2.hs:21:0-2
*Main> :i fly2
fly2 :: Travel CatName City -> String
-- Defined at ClarityTake2.hs:30:0-3
*Main> :i Travel
data Travel t1_who t2_place
= Travel {who :: t1_who, from :: t2_place, to :: t2_place}
-- Defined at ClarityTake2.hs:23:5-10
*Main> putStrLn $ fly2 (Travel {to="New York, NY", from=denver, who="Mittens"})
Mittens flies from Denver, CO to New York, NY

In the interactive session above, we can use the "info" command (:i ) to check what the type signature of a given function is. If we write our types with clarity, it then becomes obvious how we should use the function. So for example, with the function "fly", we can see we need to provide a "Who" type with CatName, a "From City" and a "To City".

Unfortunately, however, although the type names have been clearer, we still have to remember argument order if we just use plain types. This is where the record syntax comes to the rescue. The "Travel t1 t2" type holds all of the function arguments which can be obtained using the accessor functions "who", "from", and "to".

By doing a little mental gymnastics piecing together the ":i Travel" and ":i fly2" information, we can see that Travel defines "who" as a "CatName" (standing in for t1_who) and "from"/"to" as type "City" (standing in for "t2_place"). The record arrays give then give us the flexibility to define arguments in whatever order we desire. As a last example, let's look at the last call to fly2 in the ghci interactive session above. Here, Mittens (another super cat) will fly from Denver to New York. However, we were able to call the function with arguments in a different order! This could be handy.

The nice thing about both solutions (Record syntax and descriptive type constructors like "From a" and "To a") is that the code readability is greatly enhanced. One can now come upon a piece of code like the following, and quickly parse out what it's doing just by reading it and that's nice:
  • drive (Who michael) (From denver) (To ftCollins) -- descriptive type constructors
  • drive (Travel {who=michael, from=denver, to=ftCollins}) -- record syntax
Cheers!

About Me

My Photo
A comic artist, sometimes programmer, and engineer in the field of renewable energy.

Tags

© 2009, 2010 Michael Patrick O'Keefe, all rights reserved

Interesting Shtuff

Followers