Understanding pure functions and side effects in Haskell - putStrLn
It’d probably be easier to understand what the author means if we define helloWorld
as a local variable:
main :: IO ()
main = do
let helloWorld = putStrLn "Hello World!"
helloWorld
helloWorld
helloWorld
which you could compare to this C#-like pseudocode:
void Main() {
var helloWorld = {
WriteLine("Hello World!")
}
helloWorld;
helloWorld;
helloWorld;
}
I.e. in C# WriteLine
is a procedure that prints its argument and returns nothing. In Haskell, putStrLn
is a function that takes a string and gives you an action that would print that string were it to be executed. It means that there is absolutely no difference between writing
do
let hello = putStrLn "Hello World"
hello
hello
and
do
putStrLn "Hello World"
putStrLn "Hello World"
That being said, in this example the difference isn’t particularly profound, so it’s fine if you don’t quite get what the author is trying to get at in this section and just move on for now.
it works a bit better if you compare it to python
hello_world = print('hello world')
hello_world
hello_world
hello_world
The point here being that IO actions in Haskell are “real” values that don’t need to be wrapped in further “callbacks” or anything of the sort to prevent them from executing - rather, the only way to do get them to execute is to put them in a particular place (i.e. somewhere inside main
or a thread spawned off main
).
This isn’t just a parlour trick either, this does end up having some interesting effects on how you write code (for example, it’s part of the reason why Haskell doesn’t really need any of the common control structures you’d be familiar with from imperative languages and can get away with doing everything in terms of functions instead), but again I wouldn’t worry too much about this (analogies like these don’t always immediately click)
It might be easier to see the difference as described if you use a function that actually does something, rather than helloWorld
. Think of the following:
add :: Int -> Int -> IO Int
add x y = do
putStrLn ("I am adding " ++ show x ++ " and " ++ show y)
return (x + y)
plus23 :: IO Int
plus23 = add 2 3
main :: IO ()
main = do
_ <- plus23
_ <- plus23
_ <- plus23
return ()
This will print out "I am adding 2 and 3" 3 times.
In C#, you might write the following:
using System;
public class Program
{
public static int add(int x, int y)
{
Console.WriteLine("I am adding {0} and {1}", x, y);
return x + y;
}
public static void Main()
{
int x;
int plus23 = add(2, 3);
x = plus23;
x = plus23;
x = plus23;
return;
}
}
Which would print only once.