So, we're going to start off with the Fibonacci sequence...
Nah, I'm just messing with you. Let's start off with a really simple program that plays the guessing game. You'll think of a number from 1-100, and the computer will try to guess it. I'll show you how I'd build this program up from scratch.
Step 1: Hello, world!
Let's start out with a simple greeting. It's always nice to be greeted by your computer, right?
main = do putStrLn "Sup?"
Run it! If nothing bad happened, you'll get this:
Sup?
That was easy. Quick overview of what just happened:
Main
`main` is the function where Haskell programs start.
do
`do` is special notation that I'll get into in another chapter or two, once we have a more solid understanding of Haskell. Right now, you should know two things about do:
* Only use `do` in functions that do something with IO. (This is a half truth, as you can use `do` in other places too - but you wont do that for a while.) * `do` makes your code seem imperative - that is, in big do expressions your function structure looks similar to Java or C++. How is this possible in a functional language, you wonder? Think about how that could be. I'll explain in a bit.
putStrLn
Simple! Show some words on the screen.
Or is it not so simple? putStrLn is the first time we've called a function. Since we're using a functional language, just filled with functions, the Haskell guys decided it better be as easy as possible. And it is! No parenthesis, no commas. Just `someFunction 1 2 3 4`. Ah, beauty!
Step 1.5: Take a bad guess at the user's name.
main = do let yourName = "George" putStrLn("Hi, " ++ yourName)
Wouldn't it be great if your name was George? I bet I just freaked out all the Georges reading this book. Sorry, George. I didn't mean to surprise you.
What new syntax do we have?
let yourName = "George"
That's a function definition!
"That's a function definition?" you ask quizzically. "Why does it start with `let`? The only function I ever defined was `main`, and I never said `let main = blabla`. It looks a lot more like a constant."
That's true. To Haskell, constants and functions are actually one and the same. Haskell is smart enough to know that if a function only returns one value, then it should be treated like a constant.
As for the `let`, for now we'll say that that's how you define functions inside of `do` blocks. In a bit, you'll see that there is a tiny difference between `do` code and non-`do` code. --TODO: Is it really?
++
This joins two lists together. Into one MEGA LIST.
But wait, you cry! How does it work on Strings? In Haskell, Strings - like `"WAZZUP"` - are just lists of characters.
Step 1.5: A brief interlude about types.
I've skirted around this issue enough!
<Insert comic of guy hiding the IO monad from his gf. It'll be hilarious, I promise.>
One of the big deals about Haskell is its super powerful type system. You've probably heard that before, but don't know what it means. Well, let's find out! Boot up ghci and type this in.
Prelude> let x = 5
--TODO: This explanation of ghci probably deserves to be in another place. Or at least wrapped in a nice div...
Hold your horses, you cry. What the heck is ghci?! What did I just do?
If you've ever programmed in a language with a REPL before, you can skip this part. Otherwise, read on!
A REPL (stands for read-eval-print-loop) is a great way to test out writing some code without going through the whole make and build process. What it does is allow you to write one line of code at a time and see the output immediately. Try booting up ghci right now, so you can see what I'm talking about.
Prelude> 1 + 1 >> 2
--TODO: Double sidebar?
The stuff you type into ghci comes after Prelude>. To be clear, I've chosen to show what it gives back with >> even though ghci doesn't use those.
Anyway, isn't that cool? You typed in 1 + 1 and it returned 2. I mean, you probably knew that already, but isn't it neat that you can get your computer to do it for you?
--TODO: Back to book
Now, remember what I said about `do`, and how great it was for imperative programming and output stuff? Now, pure functional programming is great and all, but it would be really lame if ghci didn't let you do any input or output! So the smart Haskell guys decided that all of ghci should run inside a do block.
And that explains why your function definitions have to be declared with a let.
Now, let's get back to typing. Follow along in ghci, or just read what I do if you're lazy:
Prelude> :type x x :: Integer
Wow, cool! I asked ghci for the type of x, and it knew it was an integer, even though I didn't tell it!
Yeah, that's pretty cool, but try this on for size:
Prelude> let getHelloMessageFor someName = "Hello, " ++ someName ++ "!" Prelude> :type getHelloMessageFor getHelloMessageFor :: [Char] -> [Char]
You can read that like so: Given a Char array (remember, we said earlier that those are just strings), it'll give back a char array. That's impressive, though, isn't it? How could it possibly know that someName was a [Char]?
(Think about that a little bit before reading on.)
The answer is that Haskell is very smart. It noticed that you were ++ing someName with some other strings, and logically deduced that someName must also be a string, since if you're ++ing two things together they have to be of the same type. Cool, huh?
So the takeaway here is that Haskell is really smart, and it can figure out (it's called type inference if we wanna get fancy) the types of just about any expression that you throw at it. If you want, you can annotate your functions with the correct type information ahead of time, like so:
(Imagine that this is in some haskell file, not in ghci.)
myFunc :: Int -> Int myFunc x y = x + y
I would recommend doing this when starting out, because sometimes if you use the wrong syntax to do something then Haskell could infer the wrong type and give you weird errors.
Back to types. So there's something in Haskell called IO. If you hop into ghci and make a function that does IO, you might notice it:
Prelude> let sayHi = do putStrLn "Hi!" Prelude> :t sayHi sayHi :: IO ()
IO is essentially like saying "Hey Haskell, this function does some IO. Maybe putStrLn, maybe getLine. Maybe it even reads a file. I dunno!"
The trick is that functions with the IO type can't be called from functions that aren't IO. Although the specifics have to come later, we can get a good intuition right now. You see, Haskell prides itself on forcing you to write pure functional functions. That means that if I call `someFunc 5` and it gives me `12` the first time, and I keep doing that over and over again with 5 every time, it's never going to give me anything other than 12.
But what about `getLine`? If I call `getLine` 5 times, no matter how hard you try, you're not going to be able to force the user of your program to type in the same thing 5 times, unless you have really weird users.
So at this point, you're probably like, "Aha! `getLine` violates pure functional principals! Haskell Curry was a total fraud!"
Ah, but Haskell has a card up its sleve. Remember, functions only return the same values if they have the same arguments passed to them every time. And even though you don't appear to be passing anything into getLine, the IO that you noticed was part of your argument list is sneakily getting inserted into the call every time you make it. So the call is different every time, which is why it can return different results! (Haskell Curry cackles from behind the grave.)
What is this mysterious IO type that seems to be changing every time we look at it? Well, the simplest conceptual model of it is to think of it as the world. --TODO: Yikes, that's not simple at all!
Step 2: Take in some input.
Wouldn't it be a lot nicer if the program knew who it was greeting?
main = do putStrLn "What's your name?" str <- getLine putStrLn "Hi, " ++ str
Again, let's walk over the new syntax.
getLine
This reads a line that the user types in.
<-
This operator comes along with `do`, so you only gain access to this operator inside a function that starts with `do`.
TODO
BUT GRANT! THIS PROGRAM DOESN'T ACTUALLY WORK. DID YOU SPEND ANY TIME TESTING YOUR CODE AT ALL?!?
Oops.
test2.hs:3:2: Couldn't match expected type `[a]' against inferred type `IO ()' In the first argument of `(++)', namely `putStrLn "Hi!"' In the expression: putStrLn "Hi!" ++ str In the expression: do { let str = "George"; putStrLn "Hi!" ++ str }
Oh my God, that's terrifying, isn't it? Initially, Haskell error messages are really hard to understand. After some practice, though (oh, you'll get lots of practice, believe me), they'll start to become much more clear.
First, look at the top:
test2.hs:3:2:
This means that the error occurred in the file `test2`, on line `3`, column `2`.
So what's the rest of the error saying?
The way I think about these things is that they start really close to the error, and then they zoom out.
--TODO: I never explained how to install ghc or ghci. --TODO: I need to get a better place to host this than github... maybe?