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!
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.
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.
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.
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With