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!

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!

0 comments:

Post a Comment

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