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!