Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding pure functions and side effects in Haskell - putStrLn

Tags:

haskell

Recently, I started learning Haskell because I wanted to broaden my knowledge on functional programming and I must say I am really loving it so far. The resource I am currently using is the course 'Haskell Fundamentals Part 1' on Pluralsight. Unfortunately I have some difficulty understanding one particular quote of the lecturer about the following code and was hoping you guys could shed some light on the topic.

Accompanying Code

helloWorld :: IO ()
helloWorld = putStrLn "Hello World"

main :: IO ()
main = do
    helloWorld
    helloWorld
    helloWorld

The Quote

If you have the same IO action multiple times in a do-block, it will be run multiple times. So this program prints out the string 'Hello World' three times. This example helps illustrate that putStrLn is not a function with side effects. We call the putStrLn function once to define the helloWorld variable. If putStrLn had a side effect of printing the string, it would only print once and the helloWorld variable repeated in the main do-block wouldn't have any effect.

In most other programming languages, a program like this would print 'Hello World' only once, since the printing would happen when the putStrLn function was called. This subtle distinction would often trip up beginners, so think about this a bit, and make sure you understand why this program prints 'Hello World' three times and why it would print it only once if the putStrLn function did the printing as a side effect.

What I don't understand

For me it seems almost natural that the string 'Hello World' is printed three times. I perceive the helloWorld variable (or function?) as a sort of callback which is invoked later. What I don't understand is, how if putStrLn had a side effect, it would result in the string being printed only once. Or why it would only be printed once in other programming languages.

Let's say in C# code, I would presume it would look like this:

C# (Fiddle)

using System;

public class Program
{
    public static void HelloWorld()
    {
        Console.WriteLine("Hello World");
    }

    public static void Main()
    {
        HelloWorld();
        HelloWorld();
        HelloWorld();
    }
}

I am sure I am overlooking something quite simple or misinterpret his terminology. Any help would be greatly appreciated.

EDIT:

Thank you all for your answers! Your answers helped me get a better understanding of these concepts. I don't think it fully clicked yet, but I will revisit the topic in the future, thank you!

like image 566
Fluous Avatar asked Nov 25 '19 15:11

Fluous


People also ask

How does Haskell deal with side effects?

Haskell is a pure language Moreover, Haskell functions can't have side effects, which means that they can't effect any changes to the "real world", like changing files, writing to the screen, printing, sending data over the network, and so on.

Why does a pure function have no side effects Haskell?

A function has no side effects. Calling a function once is the same as calling it twice and discarding the result of the first call. In fact if you discard the result of any function call, Haskell will spare itself the trouble and will never call the function.

What are pure functions in Haskell?

From HaskellWiki. A function is called pure if it corresponds to a function in the mathematical sense: it associates each possible input value with an output value, and does nothing else.

What is purity in Haskell?

Haskell can do anything your mainstream programming language can. Purity is not about preventing side effects (a database query or an http request), it's about having a clear boundary between code with side effects (impure) and pure code.


1 Answers

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)

like image 63
Cubic Avatar answered Oct 14 '22 09:10

Cubic