If you want to follow along, download the Haskell Platform, which provides a Haskell compiler (ghc) and Haskell interpreter (ghci).
Hello, world!
Let's begin from the simplest possible Haskell program:
main = putStrLn "Hello, world"If you save the above program to example.hs, you can compile and run it using:
$ ghc -O2 example.hs # '-O2' is a good habit to learn [1 of 1] Compiling Main ( example.hs, example.o ) Linking example ... $ ./example Hello, world!
Types - Part 1
All Haskell values have types, but we usually don't need to supply any type signatures because the compiler can mechanically deduce the types for us.
This means we can ask the ghci Haskell interpreter to infer the types of arbitrary Haskell expressions. Let's fire up ghci so we can ask for the types of the values we used:
$ ghci GHCi, version 7.4.1: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Prelude>Let's start off with something simple. What is the type of "Hello, world!"?
Prelude> :type "Hello, world!" "Hello, world!" :: [Char]ghci says that "Hello, world!"'s type is [Char], which means "a list of characters". Char is the type of a single character and the brackets around Char mean "list of".
Wait, shouldn't it be a String? Actually, String is just a type synonym for [Char], and we can verify that using ghci's :info command:
Prelude> :info String type String = [Char] -- Defined in `GHC.Base'That means that we can use String or [Char] interchangeably in types. They are equal.
Now let's study the type of putStrLn:
Prelude> :t putStrLn -- You can use ":t" instead of ":type" putStrLn :: String -> IO ()putStrLn is a function that takes a single argument of type String and produces a value of type IO (). The IO () signifies an executable action that returns a value of type (). In this case, the action just print the given String, and putStrLn is just a mnemonic for "put String with newLine.
All IO actions must return a value, so Haskell programmers return () when they have nothing useful to return. This exactly mirrors how imperative programmers mark a function void when they don't return anything useful. For example, if we were to translate putStrLn's type to other languages, it would look like:
// C void putStrLn(char *str) // Java public static void putStrLn(String str)We can ask ghci about the types of expressions, too:
Prelude> :t putStrLn "Hello, world!" putStrLn "Hello, world!" :: IO ()This tells us what we already knew: when we supply putStrLn with a String argument we get an executable IO action which will print the supplied String. All of this so far is very straight-forward and we haven't deviated yet from traditional programming languages.
Equality
Now let's compare our original program side-by-side with equivalent main functions in imperative languages:
-- Haskell main = putStrLn "Hello, world!" // C (non-standards-conformant, to simplify the comparison) void main() { printf("Hello, world!"); } // Java public static void main(String[] args) { System.out.println("Hello, world!"); }One difference should stand out: the Haskell version does not use traditional block syntax for defining the main function. Instead it uses the equals sign, which may seems quite curious to imperative eyes.
It turns out that Haskell takes equality very seriously. When you write:
x = 5... you simply declare x to be synonymous with 5. You cannot change the value of x any more than you can change the value of the number 5. x and 5 become completely interchangeable since they both refer to the same thing. They are equal.
So when we write:
main = putStrLn "Hello, world!"... all that says is that main is nothing more than a synonym for putStrLn "Hello, world!". Therefore, they must have the same type and we can prove this by loading our program into ghci:
$ ghci example.hs GHCi, version 7.4.1: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Ok, modules loaded: Main. Prelude Main> :t putStrLn "Hello, world!" putStrLn "Hello, world!" :: IO () Prelude Main> :t main main :: IO ()
Types - Part 2
Haskell has one and only one rule for what constitutes a valid main:
- main must have type IO a (where a may be any return value)
Anything of type IO a is executable, and that rule ensures that main is executable. If I define main to be something non-executable, I get a type error:
main = 'A'
$ ghc -O2 example.hs [1 of 1] Compiling Main ( example.hs, example.o ) example.hs:1:1: Couldn't match expected type `IO t0' with actual type `Char' In the expression: main When checking the type of the function `main'The compiler tells us that a Char is not executable, which makes sense! Notice that we rejected the above main not on the basis of syntax or grammar, but solely on the basis of its type. The type alone communicates whether or not something is executable. If something has type is IO a, then it is executable. If something does not have type IO a, then it is not executable.
Wait... How does main do more than one thing, then? putStrLn "Hello, world" has type IO (), so does that mean that it uses up all of main's "executable juice"? Not at all!
do notation
Haskell lets you combine multiple IO actions into a single IO action using do notation. I'll introduce the following two functions to demonstrate this:
getLine :: IO String -- I read one line from stdin putStrLn :: String -> IO () -- We've metWe can feed the String return value of getLine into putStrLn using do notation:
main = do str <- getLine putStrLn strHaskell also supports semicolons and braces for those who don't like significant whitespace:
main = do { str <- getLine; putStrLn str; }do notation combines these two IO actions into a single IO action. This combined action prompts the user for one line of input and then echoes it back. Let's try it!
$ ./example Test<Enter> TestWe can also sequence two IO actions like this:
main = do putStrLn "Hello," putStrLn "world!"
./example Hello, world!We can combine as many actions as we please:
main = do putStrLn "Enter a string:" str <- getLine putStrLn str
$ ./example Enter a string: Apple<Enter> AppleOur do block only lets us combine IO commands, though. If we attempt to insert a non-IO statement, we get a type error:
main = do putStrLn "Enter a string:" str <- getLine "Hello"
example.hs:3:11: Couldn't match expected type `IO b0' with actual type `[Char]' In a stmt of a 'do' block: "Hello" In the expression: do { putStrLn "Enter a string:"; str <- getLine; "Hello" } In an equation for `main': main = do { putStrLn "Enter a string:"; str <- getLine; "Hello" }The first line says it all. The do block expected another executable command on the last line, but we gave it a String, which isn't executable. That's a pretty reasonable type error if you ask me.
Types - Part 3
Haskell has one and only one rule for deciding if a do block is valid:
-- Given: m :: IO a f :: a -> IO b -- Then the following 'do' block is valid block :: IO b -- This must match the return value of 'f' block = do a <- m f ado blocks are first class expressions in Haskell. Like all expressions, we can ask for ghci for their type:
Prelude> :t do { str <- getLine; putStrLn str; } do { str <- getLine; putStrLn str; } :: IO ()putStrLn str was the last action and it's type is IO (), so according to the rule the whole block has the same type: IO ().
Now, why on earth should a do block have a return value at all? That would only make sense if we could use a do block within another do block. Does that work?
main = do str <- do putStrLn "Enter a string:" getLine putStrLn str
$ ./example Enter a string: Apple<Enter> AppleHuh.
do blocks use the same return value as their last command because they actually return the last command's value if you bind them within a larger do block. In the above example, the inner do block forwards the String returned by getLine to the outer do block, and that String is bound to the str variable.
This approach maintains consistency in the face of refactorings. Let's define a synonym for the inner do block so we can refer to it by name:
main = do str <- prompt putStrLn str prompt :: IO String prompt = do putStrLn "Enter a string:" getLineprompt's type signature is entirely for our own benefit. Remember that the compiler can deduce the type mechanically. Haskell programmers still like to include these type signatures to organize their thoughts or to clearly communicate their code's intention to other programmers. prompt's type signature tells other programmers "This is an executable action that retrieves a String".
Impurity
Notice how we don't use the equals sign when we store the result of IO actions. Instead we use the <- symbol:
main = do str <- prompt ...That's because str is not truly equal to prompt. In fact, they don't even have the same type. str has type String and prompt has type IO String. When we mark something as IO a, we communicate that its result may change each time we bind its result within a do block.
That is obvious in the case of prompt since the user may enter a different string each time:
main = do str1 <- prompt putStrLn str1 str2 <- prompt putStrLn str2 prompt :: IO String prompt = do putStrLn "Enter a string:" getLine
Enter a string: String 1 String 1 Enter a string: String 2 String 2However, notice that we still use the equal sign when defining prompt:
prompt :: IO String prompt = do putStrLn "Enter a string:" getLineprompt is truly equal to that executable action, which seems strange. How does that reconcile with what I just said?
It turns out that Haskell distinguishes between an executable program and the program's return value, a distinction that almost no other language makes. A value of type IO a is an executable program that retrieves an a, possibly with side effects, but the program itself is not the same as its return value. So if getLine has type IO String, that means that getLine is an executable program that knows how to retrieve a String. It does not mean that getLine is a String
When I say prompt equals the given do block, I mean that they are equal in the sense that they are the same program. Since they are equal I can freely substitute one for the other. For example, I can go back and substitute the do block in place of prompt and trust that this substitution does not change anything:
main = do str1 <- do putStrLn "Enter a string:" getLine putStrLn str1 str2 <- do putStrLn "Enter a string:" getLine putStrLn str2This means that do notation does not actually do any computation. All it does is combine program descriptions into larger program descriptions. The compiler then translates the main program description into an executable.
This might seem like a trivial distinction at first until you realize that this allows you to separate your program into two parts:
- The part you reason about at compile-time (i.e. non-IO code)
- The part you reason about at run-time (i.e. IO code)
With traditional programming languages, all code only exists at run-time (i.e. it is all IO code by default), so you can never be entirely sure that it is truly correct. No matter how many tests you write, all you can prove to yourself is that a certain run-time snapshot of your program happens to be in a correct state.
Associativity
Haskell guarantees the following property of all IO programs:
-- Given: m :: IO a f :: a -> IO b g :: b -> IO c do b <- do a <- m f a g b = do a <- m b <- f a g b = do a <- m do b <- f a g bIn other words, the program's behavior does not change no matter how you group IO actions. They will still always execute in the same order and preserve the same flow of information.
This guarantees that we can always safely refactor arbitrary code blocks without worrying that we will get weird behavior as a result of creating or removing blocks. Using the above example, I could choose to factor out the first two actions:
block12 = do a <- m f a block123 = do b <- block12 g b... or I can factor out the last two actions:
block23 a = do b <- f a g b block123 = do a <- m block23 aI can similarly trust that "unfactoring" things is safe and won't cause weird behavior. In other words, I can inline the two refactored blocks safely and trust that it produces the same program:
block123 = do a <- m b <- f a g bNotice that all these programs are all truly equal, which is why we use the equal sign when we say they are the same. This is an example of how we reason about correctness at compile time. We can't reason about the specific values of a, b, and c because they do not exist until run-time, but the program descriptions themselves exist at compile time, so we can statically reason about their correctness and prove that certain transformations are correct.
Identity
What if we want to create a "trivial" program that just returns a value without any side effects? That's what the return function does:
return :: a -> IO areturn takes a value and then defines a trivial program that has no side effects and just returns the given value.
Note that it does not behave like the traditional return command in imperative languages. It does not escape from the surrounding block because Haskell do notation has no concept of a "surrounding block". If it did, all the transformations I gave in the last section would not be correct. More generally, return does not affect control flow in any way. All it does is create an "empty" program that yields a value. That's it.
We call it return because we commonly use it as the last statement in a do block to combine the results of several previous IO actions:
twoStrings :: IO (String, String) twoStrings = do str1 <- getLine str2 <- getLine return (str1, str2)Other than that, it bears no resemblance to traditional returns.
What equalities might we expect return to satisfy? Well, we expect that:
do x <- m return x = do mIn other words, there is no need to "re-return" the last action's value, because do notation already does that.
We also expect that:
do y <- return x f y = do f xThis just formalizes what I already said: return is an empty program that just binds the same value that you gave it.
Ordering
Now let's define two programs that retrieve input from the outside world:
getLine :: IO String -- Provided by the Prelude getInt :: IO Int getInt = do str <- getLine return (read str)... and then a function designed to use both of those values:
f :: String -> Int -> IO () f str n = do if (n > 0) then putStrLn "n is positive" else putStrLn "n is not positive" putStrLn strNew Haskell programmers will commonly make the following mistake:
main = f getLine getInt... which the compiler will promptly correct:
example.hs:12:10: Couldn't match expected type `String' with actual type `IO String' In the first argument of `f', namely `getLine' In the expression: f getLine getInt In an equation for `main': main = f getLine getIntImperative programmers make this mistake because imperative languages let them write:
f(getLine(), getInt())... and the imperative compiler doesn't mind that you passed an impure function as an argument because imperative languages don't distinguish between the side effects and their result.
The imperative approach may seem acceptable until you ask yourself the question: "Which function will prompt the user first: getLine() or getInt()? Typically an imperative language will evaluate the arguments in order, first evaluating getLine followed by getInt. However, this evaluation order is entirely arbitrary and requires extending the language definition.
In fact, most imperative programmers would probably consider the above imperative example "bad form". We'd rather make the ordering more explicit by writing:
str = getLine(); // First prompt the user for a string n = getInt(); // Now prompt the user for an int f(str, n); // Now use both valuesHaskell differs from imperative languages by forcing you to explicitly specify the order of all computations with side effects, so that there is never any ambiguity. Haskell forces us to write:
main = do str <- getLine n <- getInt f str nIn other words, Haskell requires imperative best practices by forcing the programmer to order all side effects and not rely on any implicit ordering by the language. This improves the readability of Haskell code because you can always reason about the order of side effects: a do block guarantees that side effects always order from top to bottom.
Miscellany
Haskell prides itself on being an orthogonal language. Almost all concepts are just syntactic sugar on just a few core concepts. This section just describes a few last syntactic niceties that Haskell provides that build entirely on top of the previous concepts.
First off, when we sequence two commands:
do command1 command2... that is just equivalent to discarding the result of the first action:
do _ <- command1 command2Also, a do block with one command is just equal to that command:
main = do putStrLn "Hello, world!" -- is equivalent to: main = putStrLn "Hello, world!"Finally, you can define pure computations within a do block using let:
do let x = y f x = do f yFor example:
main = do str <- getLine let response = "You entered: " ++ str putStrLn response {- equivalent to: main = do str <- getLine putStrLn ("You entered: " ++ str) -}
$ ./example Orange<Enter> You entered: Orange
Conclusions
Actually, all of that is just the tip of the iceberg because do notation is also just syntactic sugar! If you want to learn more about that, then I recommend you read You Could Have Invented Monads, which explains how do notation itself is built on top of an even more primitive core concept. The more you learn Haskell, the more you realize how it is just a bunch of syntactic sugar on a very small core of orthogonal concepts.