Wednesday, August 15, 2012

Haskell Chapter 1.2.3

Your first assignment pt2: Program your Wizard


Warning: This may turn out to be the hardest chapter in the entire online book! It's filled with rules upon rules; which nobody likes to read. However, once you are through with Chapter 1, you will have a strong working-knowledge of practical Haskell. Stick with it and I'll keep looking for better ways to rewrite the following article!

    One if the best things about functional programming is that it forces you to separate your code into logical chunks. This is great because you can look at the code and immediately tell what each chunk does. It's also frustrating because Haskell enforces this mentality rather strictly; you must undergo a few disheartening failures before you get to experience success. Here's the code I used for this simple little wizard:

main = do
  age <- askUser
  prediction age

askUser = do  
  putStrLn "Tell me your age and I'll magically predict your mother's age: "  
  getLine

addToAge x = read x + 25

prediction z = do 
  putStrLn "Your mom's current age is: "
  print $ addToAge z


    If you come from a dynamic programming background, you will already see that it takes a LOT more code to implement this program in Haskell than it does in a good dynamic language; Haskell isn't always more elegant

    What you see above are four discreet functions. The overall program is referred to in Haskell as a module. So, here we have four functions which work together to create one module. 

    The first function is the 'main = do' module. This is the spine. Notice how each line after 'do' is indented. Also notice that each of the four functions are composed mostly of IO actions like 'putStrLn'. The indentation is very important in 'do' functions. Make sure you always check it before you try to compile your finished program.

    So, logically you can break this whole 'momsAge' module down into two core activities:
  1. Ask your classmate how old she is and write that number down.
  2. Enter your classmate's age into a function and speak the results to her.
    Likewise, 'main' is broken into two functions that reflect this: 'age <- askUser' takes care of activity number one. 'prediction age' takes care of activity number two.

    If you look closely at both activities above, you will see they each consist of two more separate sub-activities. So, following these two logical core activites as they break down into two more subserviant activities we can directly construct the code above. Of course, it's not that easy. Haskell has all those rules we talked about earlier. Here's the break down of the 'momsAge' code using each of the 10 IO Rules of Haskell that apply; starting with the 'main=do' chunk:


main = do
  age <- askUser
  prediction age


Rule #1: Gotta have a 'main=do' function.
Rule #2: Make sure you indent each line in the function's definition by exactly two spaces.
Rule #3: Separate your input function from 'main'. For now, we can call the input function: 'askUser' because it asks the user for her age.
Rule #4: Bind the output of 'askUser' to a variable within 'main' -- in this case we call the variable 'age' because terse variable names like 'x' are practically useless. Otherwise, Haskell will not save the results of 'askUser'; and you won't be able to use it for our second 'core activity'.
Rule #7: The last line of a 'main=do' function needs to return some kind of value. The 'prediction' function prints out the results along with a message to the user. This qualifies as a 'return value'.

     Whew! See what I mean about rules on top of rules! All of the rules above and reasoning we made earlier about the logical structure of the program come together to make this little function.

    If you have read the other non-CS sections of this blog, you'll know that this is a prime example of symbolic leveraging gone wrong. Anyways, we're still not done. We need to define 'askUser' and 'prediction'; right now they're just meaningless placeholders. We'll start with 'askUser' because it's first in the logical order. Here's the rules that govern 'askUser':


askUser = do  
  putStrLn "Tell me your age and I'll magically predict your mother's age: "  
  getLine


Rule #2: Make sure you indent each line by exactly two spaces.
Rule #3: Separate your input function from 'main'. We can call the overall input function: 'askUser' because it asks the user for her age. However, the input 'action' which retrieves the user-data is 'getLine'.
Rule #6: 'askUser' has an IO action inside of it. In fact, it is basically nothing-but IO actions. That means we need to employ the magic 'do' word.
Rule #7: The last line of a 'do' function needs to return some kind of value. 'getLine' takes the user's keyboard input and immediately returns it to whoever invoked it (in this case 'askUser'). If you run 'askUser' from a ghci interactive session (whose parent function is 'std IO'), it will return the output value on screen (which is also 'std IO'). Likewise, if you run 'askUser' from inside of another function, it will return the output value to that other function (in this case 'main=do'). This is why Rule #4 works!

And now the rules that govern the next chunk of code called 'addToAge':

addToAge x = read x + 25

Rule #5: 'addToAge' has no IO actions. 'read' sure seems like an IO action, but Haskell doesn't require 'do' notation for read. Since 'read' is not an IO action, we must follow rule number five and separate the whole operation off to it's own function. This is nice, if the median age of new motherhood increases, or if we want to give the result as a range instead of one answer (which is bound to be wrong) all we have to do is change 'addToAge'. The rest of the 'momsAge' module remains intact and bug free! This is what functional programming is all about!
Rule #9: We know that 'addToAge' is going to receive a variable which has been bound to the output of 'askUser'. We also know that 'askUser' uses 'getLine' which only delivers strings for outputs; regardless of what kind of input you give it. Since Haskell is statically-typed, we know that it will refuse to perform arithmetic operations on strings -- even if they look like numbers to you and me. What we need then is some way to convert our variable (age) into a number long enough to perform arithmetic on it.
Rule #10: 'read' is a way to convert a string to a number and back again. It doesn't work for a string of letters like 'bananas', but it works great for a string of numbers like '20'. So, using rule #9, we anticipated that 'age' would be a string. Now we apply 'read' to age, and continue to operate on it using arithmetic. The output of 'addToAge' will still be a string, but 'read' allows us to calculate it as if it were a number. 

And finally (make it stop please!), the rules that govern 'prediction':


prediction z = do 
  putStrLn "Your mom's current age is: "
  print $ addToAge z


Rule #2: Make sure you indent each line by exactly two spaces.
Rule #6: 'putStrLn' is an IO action, so it means that 'prediction' requires 'do' notation.
Rule #7: We need to finish with an output function. The logical choice is to finish with 'addToAge' because we know it outputs a concrete value (x + 25).
Rule #8: We also need a way to print the output of 'addToAge'. We know 'addToAge' is not a 'do' function; and thus it has no IO capabilities of it's own. However, 'print $' gives us a tool to print the output of any function onscreen AND it satisfies rule #7 at the same time!

Wrapping it all up: Save, Compile and Load your Module


Whew! All done! Now all we need to do is save the code to a '.hs' file and load it into ghci. Here's how: 

Copy the text above to a good text-editor (I use KATE). Make sure that there are no empty lines or spaces before 'main=do'; remember Haskell is very sensitive to spacing and indents.

Next, save the file as 'momsAge.hs' within the haskellPrograms folder we created earlier. Open a terminal, navigate to /haskellPrograms and type 'ghci'. When  you see the 'Prelude>' prompt, type: 

':l momsAge'

As long as there are no weird empty lines or spacing issues, the program will compile and you will be greeted with this prompt:

*Main>

This means that ghci has loaded-up 'momsAge'. 'Main>' is sort of a generic-prompt which Haskell uses for user-created functions. To run your module, you need to 'call' the main=do function by typing 'main' (lowercase m). Of course, you can call other functions in 'momsAge' too (if you feel like testing them out) by simply typing the name of the function and pressing enter. From there, it's pretty self-explanatory.

Prologue 


    Wow, that was a helluva lot of rules to remember. I agree. Way too many in fact. But, once you master this section, we can go on and practice writing programs instead of spending the entire book ONLY talking about theory (like in Learn You a Haskell and Haskell:TCoFP 1st Ed). Its a pain and you will probably need to re-read this section several times. However, there really is no other way to do this. This leads to the last rule (for now), but don't worry; it's easy to remember:

The Golden Rule of Coding: The only way to learn how to code is to code, code, code!

In the next section we will concentrate only on practical IO excercises until (hopefully) it becomes natural for you. In section 1.3 we will introduce Bools briefly, and then finish with some more exercises. All that theory and verbose explanation is great -- to a point. But you need to practice until your fingers 'just know what to do' without even thinking about rules.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.