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.

Tags:

Haskell