1

I'm sure this must have been asked before, but I can't find anywhere that actually answers my question, so apologies if I have simply overlooked this.

I am currently learning Haskell, and loving the way it balances flexibility allowing rapid prototyping with functional purity. However I'm trying to get my head around best practices. One thing I'm not currently sure of is, say you have something like the following:

worlds :: [Foo] -> [World]
worlds [] = []
worlds fs = map toWorld fs

This function relies on a toWorld function that would have been defined elsewhere. My immediate reaction here is that because you are interacting with something "global", is it "correct" to do this? Or would you better to pass toWorld into the function. Now, say this was Javascript, obviously you could do something like:

function toWorld (foo) {
    console.log('Side effect');
    return foo; // The actual transformation is irrelevant
}

In this case, my understanding is that the purity of worlds is simply dependent on the purity of toWorld, and with an implementation such as this, worlds would become impure because toWorld has a side-effect. Of course in Haskell, where such side-effects are impossible, you are guaranteed that toWorld will always be pure, so this "makes it okay"? (If that makes sense)

Furthermore, say that toWorld not only took a foo, but also took some constant that influenced the transformation. So you had something like:

constant = 5

toWorld :: Int -> Foo -> World
toWorld k f = World k f -- Something, again, irrelevant

worlds :: [Foo] -> [World]
worlds [] = []
worlds fs = map (toWorld constant) fs

Is this now "impure"? Should the constant be something that is always passed to worlds? Because surely someone could just redefine constant to something else at runtime and change how the function works?

Hope all that makes sense. Wanted to get some thoughts out. Any input would appreciated, no matter how small. Cheers!

4 Answers 4

8

Is this now "impure"? Should the constant be something that is always passed to worlds?

In your second example, the function worlds is actually a closure that references the variable constant defined in its context. This does not influence its purity because worlds still respects referential transparency, i.e. you can replace any invocation worlds fs by its result without changing the semantics of the program.

So, after capturing the global name constant, world is frozen into a pure function.

Because surely someone could just redefine constant to something else at runtime and change how the function works?

You cannot redefine a constant at runtime in Haskell: once a constant has been compiled, it retains its value. There is no such thing as an assignment statement in Haskell. You also cannot redefine a constant in the source code because the compiler will complain: Multiple declarations of ...

However, you can apparently redefine a constant in the REPL using let. For example:

Prelude> let x = 1
Prelude> x
1
Prelude> f y = y + x
Prelude> f 6
7
Prelude> let x = 3
Prelude> x
3

Now you might think that x has the new value 3 and this could influence f, but in fact you have defined two xs and the second one has shadowed the first one. The closure f still sees the x that is in scope where f is defined, so now you still have

Prelude> f 6
7

because f is frozen the moment it is defined.

4

Constants, be they other function definitions or data constants, don't influence purity or functional design.

1

No, this has nothing to do with function purity. The operation that toWorld performs could just as well have been defined within worlds and then the question would not even arise. The fact that it's a separate function doesn't change anything about that. Function pureness is about not depending on implicit data values, not about not depending on other functions.

4
  • Even if those other functions depend on implicit data values? Commented Sep 23, 2018 at 13:39
  • 1
    @candied_orange a function that invokes an impure function is impure, whether that function is passed as a parameter or is global.
    – Jules
    Commented Sep 23, 2018 at 18:12
  • @Jules that sounds better. Not sure implicit vs explicit is really the best way to explain side effects. Commented Sep 23, 2018 at 18:43
  • @candied_orange: "Not sure implicit vs explicit is really the best way to explain side effects.": It is not, e.g. in C++ you can pass an object by reference (&) and then modify it. The function or method that changes the object is not pure.
    – Giorgio
    Commented Sep 27, 2018 at 5:26
0

Haskell is a pure language. Everything is pure unless you're working in the IO Monad. This is the only place for you to actually interact with the world. In other words if a function does not return an IO of something,\ then it is pure. Defining constants and all else are just questions of design, not purity.

All in all purity constrains in Haskell are great. They enable the compiler to do very smart optimizations and most importantly stop you, the programmer, from doing hacky state changes that the OO world is infamously full of. These restrictions are one of the reasons why you get parallelism for free in Haskell - the CPU can work on different code parts and have a guarantee that no other code is going to change some state somewhere.

On the other hand Haskell purity (and laziness) forces a person to take a truly functional approach to coding. The language makes it much more restricted in causing side effects, even if you want to. Even simple looking things like printing values to the screen require some work (unless you're using Debug.Trace's trace). Things like these make the learning curve rather steep but if you want to learn functional programming Haskell is a great place to start.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.